KEYCLOAK-9756 PS256 algorithm support for token signing and validation
This commit is contained in:
parent
b4973ad7b5
commit
9b3e297cd0
34 changed files with 757 additions and 364 deletions
|
@ -27,6 +27,9 @@ public interface Algorithm {
|
|||
String ES256 = "ES256";
|
||||
String ES384 = "ES384";
|
||||
String ES512 = "ES512";
|
||||
String PS256 = "PS256";
|
||||
String PS384 = "PS384";
|
||||
String PS512 = "PS512";
|
||||
|
||||
String AES = "AES";
|
||||
}
|
||||
|
|
|
@ -27,6 +27,9 @@ public class JavaAlgorithm {
|
|||
public static final String ES256 = "SHA256withECDSA";
|
||||
public static final String ES384 = "SHA384withECDSA";
|
||||
public static final String ES512 = "SHA512withECDSA";
|
||||
public static final String PS256 = "SHA256withRSAandMGF1";
|
||||
public static final String PS384 = "SHA384withRSAandMGF1";
|
||||
public static final String PS512 = "SHA512withRSAandMGF1";
|
||||
public static final String AES = "AES";
|
||||
|
||||
public static final String SHA256 = "SHA-256";
|
||||
|
@ -53,6 +56,12 @@ public class JavaAlgorithm {
|
|||
return ES384;
|
||||
case Algorithm.ES512:
|
||||
return ES512;
|
||||
case Algorithm.PS256:
|
||||
return PS256;
|
||||
case Algorithm.PS384:
|
||||
return PS384;
|
||||
case Algorithm.PS512:
|
||||
return PS512;
|
||||
case Algorithm.AES:
|
||||
return AES;
|
||||
default:
|
||||
|
@ -81,6 +90,12 @@ public class JavaAlgorithm {
|
|||
return SHA384;
|
||||
case Algorithm.ES512:
|
||||
return SHA512;
|
||||
case Algorithm.PS256:
|
||||
return SHA256;
|
||||
case Algorithm.PS384:
|
||||
return SHA384;
|
||||
case Algorithm.PS512:
|
||||
return SHA512;
|
||||
case Algorithm.AES:
|
||||
return AES;
|
||||
default:
|
||||
|
|
|
@ -34,6 +34,9 @@ public enum Algorithm {
|
|||
RS256(AlgorithmType.RSA, new RSAProvider()),
|
||||
RS384(AlgorithmType.RSA, new RSAProvider()),
|
||||
RS512(AlgorithmType.RSA, new RSAProvider()),
|
||||
PS256(AlgorithmType.RSA, null),
|
||||
PS384(AlgorithmType.RSA, null),
|
||||
PS512(AlgorithmType.RSA, null),
|
||||
ES256(AlgorithmType.ECDSA, null),
|
||||
ES384(AlgorithmType.ECDSA, null),
|
||||
ES512(AlgorithmType.ECDSA, null)
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright 2019 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 PS256ClientSignatureVerifierProviderFactory implements ClientSignatureVerifierProviderFactory {
|
||||
|
||||
public static final String ID = Algorithm.PS256;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientSignatureVerifierProvider create(KeycloakSession session) {
|
||||
return new AsymmetricClientSignatureVerifierProvider(session, Algorithm.PS256);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright 2019 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 PS256SignatureProviderFactory implements SignatureProviderFactory {
|
||||
|
||||
public static final String ID = Algorithm.PS256;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SignatureProvider create(KeycloakSession session) {
|
||||
return new AsymmetricSignatureProvider(session, Algorithm.PS256);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright 2019 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 PS384ClientSignatureVerifierProviderFactory implements ClientSignatureVerifierProviderFactory {
|
||||
|
||||
public static final String ID = Algorithm.PS384;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientSignatureVerifierProvider create(KeycloakSession session) {
|
||||
return new AsymmetricClientSignatureVerifierProvider(session, Algorithm.PS384);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright 2019 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 PS384SignatureProviderFactory implements SignatureProviderFactory {
|
||||
|
||||
public static final String ID = Algorithm.PS384;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SignatureProvider create(KeycloakSession session) {
|
||||
return new AsymmetricSignatureProvider(session, Algorithm.PS384);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright 2019 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 PS512ClientSignatureVerifierProviderFactory implements ClientSignatureVerifierProviderFactory {
|
||||
|
||||
public static final String ID = Algorithm.PS512;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientSignatureVerifierProvider create(KeycloakSession session) {
|
||||
return new AsymmetricClientSignatureVerifierProvider(session, Algorithm.PS512);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright 2019 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 PS512SignatureProviderFactory implements SignatureProviderFactory {
|
||||
|
||||
public static final String ID = Algorithm.PS512;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SignatureProvider create(KeycloakSession session) {
|
||||
return new AsymmetricSignatureProvider(session, Algorithm.PS512);
|
||||
}
|
||||
|
||||
}
|
|
@ -55,9 +55,10 @@ public interface Attributes {
|
|||
"16", "24", "32", "64", "128", "256", "512");
|
||||
|
||||
String ALGORITHM_KEY = "algorithm";
|
||||
|
||||
ProviderConfigProperty RS_ALGORITHM_PROPERTY = new ProviderConfigProperty(ALGORITHM_KEY, "Algorithm", "Intended algorithm for the key", LIST_TYPE,
|
||||
Algorithm.RS256,
|
||||
Algorithm.RS256, Algorithm.RS384, Algorithm.RS512);
|
||||
Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.PS256, Algorithm.PS384, Algorithm.PS512);
|
||||
|
||||
ProviderConfigProperty HS_ALGORITHM_PROPERTY = new ProviderConfigProperty(ALGORITHM_KEY, "Algorithm", "Intended algorithm for the key", LIST_TYPE,
|
||||
Algorithm.HS256,
|
||||
|
|
|
@ -59,7 +59,7 @@ public class GeneratedRsaKeyProviderFactory extends AbstractRsaKeyProviderFactor
|
|||
|
||||
@Override
|
||||
public boolean createFallbackKeys(KeycloakSession session, KeyUse keyUse, String algorithm) {
|
||||
if (keyUse.equals(KeyUse.SIG) && (algorithm.equals(Algorithm.RS256) || algorithm.equals(Algorithm.RS384) || algorithm.equals(Algorithm.RS512))) {
|
||||
if (keyUse.equals(KeyUse.SIG) && isSupportedRsaAlgorithm(algorithm)) {
|
||||
RealmModel realm = session.getContext().getRealm();
|
||||
|
||||
ComponentModel generated = new ComponentModel();
|
||||
|
@ -81,6 +81,15 @@ public class GeneratedRsaKeyProviderFactory extends AbstractRsaKeyProviderFactor
|
|||
}
|
||||
}
|
||||
|
||||
private boolean isSupportedRsaAlgorithm(String algorithm) {
|
||||
return algorithm.equals(Algorithm.RS256)
|
||||
|| algorithm.equals(Algorithm.PS256)
|
||||
|| algorithm.equals(Algorithm.RS384)
|
||||
|| algorithm.equals(Algorithm.PS384)
|
||||
|| algorithm.equals(Algorithm.RS512)
|
||||
|| algorithm.equals(Algorithm.PS512);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel model) throws ComponentValidationException {
|
||||
super.validateConfiguration(session, realm, model);
|
||||
|
|
|
@ -4,3 +4,6 @@ org.keycloak.crypto.RS512ClientSignatureVerifierProviderFactory
|
|||
org.keycloak.crypto.ES256ClientSignatureVerifierProviderFactory
|
||||
org.keycloak.crypto.ES384ClientSignatureVerifierProviderFactory
|
||||
org.keycloak.crypto.ES512ClientSignatureVerifierProviderFactory
|
||||
org.keycloak.crypto.PS256ClientSignatureVerifierProviderFactory
|
||||
org.keycloak.crypto.PS384ClientSignatureVerifierProviderFactory
|
||||
org.keycloak.crypto.PS512ClientSignatureVerifierProviderFactory
|
||||
|
|
|
@ -7,3 +7,6 @@ org.keycloak.crypto.HS512SignatureProviderFactory
|
|||
org.keycloak.crypto.ES256SignatureProviderFactory
|
||||
org.keycloak.crypto.ES384SignatureProviderFactory
|
||||
org.keycloak.crypto.ES512SignatureProviderFactory
|
||||
org.keycloak.crypto.PS256SignatureProviderFactory
|
||||
org.keycloak.crypto.PS384SignatureProviderFactory
|
||||
org.keycloak.crypto.PS512SignatureProviderFactory
|
|
@ -22,6 +22,7 @@ import org.jboss.resteasy.spi.BadRequestException;
|
|||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.common.util.KeyUtils;
|
||||
import org.keycloak.common.util.PemUtils;
|
||||
import org.keycloak.crypto.Algorithm;
|
||||
import org.keycloak.crypto.AsymmetricSignatureSignerContext;
|
||||
import org.keycloak.crypto.KeyType;
|
||||
import org.keycloak.crypto.KeyWrapper;
|
||||
|
@ -29,7 +30,6 @@ import org.keycloak.crypto.SignatureSignerContext;
|
|||
import org.keycloak.jose.jwk.JSONWebKeySet;
|
||||
import org.keycloak.jose.jwk.JWK;
|
||||
import org.keycloak.jose.jwk.JWKBuilder;
|
||||
import org.keycloak.jose.jws.Algorithm;
|
||||
import org.keycloak.jose.jws.JWSBuilder;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.testsuite.rest.TestApplicationResourceProviderFactory;
|
||||
|
@ -77,21 +77,24 @@ public class TestingOIDCEndpointsApplicationResource {
|
|||
String keyType = null;
|
||||
|
||||
switch (jwaAlgorithm) {
|
||||
case org.keycloak.crypto.Algorithm.RS256:
|
||||
case org.keycloak.crypto.Algorithm.RS384:
|
||||
case org.keycloak.crypto.Algorithm.RS512:
|
||||
case Algorithm.RS256:
|
||||
case Algorithm.RS384:
|
||||
case Algorithm.RS512:
|
||||
case Algorithm.PS256:
|
||||
case Algorithm.PS384:
|
||||
case Algorithm.PS512:
|
||||
keyType = KeyType.RSA;
|
||||
keyPair = KeyUtils.generateRsaKeyPair(2048);
|
||||
break;
|
||||
case org.keycloak.crypto.Algorithm.ES256:
|
||||
case Algorithm.ES256:
|
||||
keyType = KeyType.EC;
|
||||
keyPair = generateEcdsaKey("secp256r1");
|
||||
break;
|
||||
case org.keycloak.crypto.Algorithm.ES384:
|
||||
case Algorithm.ES384:
|
||||
keyType = KeyType.EC;
|
||||
keyPair = generateEcdsaKey("secp384r1");
|
||||
break;
|
||||
case org.keycloak.crypto.Algorithm.ES512:
|
||||
case Algorithm.ES512:
|
||||
keyType = KeyType.EC;
|
||||
keyPair = generateEcdsaKey("secp521r1");
|
||||
break;
|
||||
|
@ -193,12 +196,15 @@ public class TestingOIDCEndpointsApplicationResource {
|
|||
boolean ret = false;
|
||||
switch (signingAlgorithm) {
|
||||
case "none":
|
||||
case org.keycloak.crypto.Algorithm.RS256:
|
||||
case org.keycloak.crypto.Algorithm.RS384:
|
||||
case org.keycloak.crypto.Algorithm.RS512:
|
||||
case org.keycloak.crypto.Algorithm.ES256:
|
||||
case org.keycloak.crypto.Algorithm.ES384:
|
||||
case org.keycloak.crypto.Algorithm.ES512:
|
||||
case Algorithm.RS256:
|
||||
case Algorithm.RS384:
|
||||
case Algorithm.RS512:
|
||||
case Algorithm.PS256:
|
||||
case Algorithm.PS384:
|
||||
case Algorithm.PS512:
|
||||
case Algorithm.ES256:
|
||||
case Algorithm.ES384:
|
||||
case Algorithm.ES512:
|
||||
ret = true;
|
||||
}
|
||||
return ret;
|
||||
|
|
|
@ -1205,7 +1205,7 @@ public class OAuthClient {
|
|||
PublicKey publicKey = JWKParser.create(k).toPublicKey();
|
||||
|
||||
KeyWrapper key = new KeyWrapper();
|
||||
key.setKid(key.getKid());
|
||||
key.setKid(k.getKeyId());
|
||||
key.setAlgorithm(k.getAlgorithm());
|
||||
key.setVerifyKey(publicKey);
|
||||
key.setUse(KeyUse.SIG);
|
||||
|
|
|
@ -23,7 +23,6 @@ import java.security.PublicKey;
|
|||
import java.security.Signature;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
|
@ -32,9 +31,11 @@ import org.keycloak.admin.client.Keycloak;
|
|||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.common.util.Base64;
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.crypto.Algorithm;
|
||||
import org.keycloak.crypto.JavaAlgorithm;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.keys.GeneratedEcdsaKeyProviderFactory;
|
||||
import org.keycloak.keys.GeneratedRsaKeyProviderFactory;
|
||||
import org.keycloak.keys.KeyProvider;
|
||||
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
|
@ -47,9 +48,6 @@ import org.keycloak.testsuite.arquillian.TestContext;
|
|||
public class TokenSignatureUtil {
|
||||
private static Logger log = Logger.getLogger(TokenSignatureUtil.class);
|
||||
|
||||
private static final String COMPONENT_SIGNATURE_ALGORITHM_KEY = "token.signed.response.alg";
|
||||
|
||||
private static final String ECDSA_ELLIPTIC_CURVE_KEY = "ecdsaEllipticCurveKey";
|
||||
private static final String TEST_REALM_NAME = "test";
|
||||
|
||||
public static void changeRealmTokenSignatureProvider(Keycloak adminClient, String toSigAlgName) {
|
||||
|
@ -58,10 +56,8 @@ public class TokenSignatureUtil {
|
|||
|
||||
public static void changeRealmTokenSignatureProvider(String realm, Keycloak adminClient, String toSigAlgName) {
|
||||
RealmRepresentation rep = adminClient.realm(realm).toRepresentation();
|
||||
Map<String, String> attributes = rep.getAttributes();
|
||||
log.tracef("change realm test signature algorithm from %s to %s", attributes.get(COMPONENT_SIGNATURE_ALGORITHM_KEY), toSigAlgName);
|
||||
log.tracef("change realm test signature algorithm from %s to %s", rep.getDefaultSignatureAlgorithm(), toSigAlgName);
|
||||
rep.setDefaultSignatureAlgorithm(toSigAlgName);
|
||||
rep.setAttributes(attributes);
|
||||
adminClient.realm(realm).update(rep);
|
||||
}
|
||||
|
||||
|
@ -88,17 +84,48 @@ public class TokenSignatureUtil {
|
|||
return verifier.verify(jws.getSignature());
|
||||
}
|
||||
|
||||
public static void registerKeyProvider(String ecNistRep, Keycloak adminClient, TestContext testContext) {
|
||||
registerKeyProvider(TEST_REALM_NAME, ecNistRep, adminClient, testContext);
|
||||
public static void registerKeyProvider(String jwaAlgorithmName, Keycloak adminClient, TestContext testContext) {
|
||||
registerKeyProvider(TEST_REALM_NAME, jwaAlgorithmName, adminClient, testContext);
|
||||
}
|
||||
|
||||
public static void registerKeyProvider(String realm, String ecNistRep, Keycloak adminClient, TestContext testContext) {
|
||||
public static void registerKeyProvider(String realm, String jwaAlgorithmName, Keycloak adminClient, TestContext testContext) {
|
||||
switch(jwaAlgorithmName) {
|
||||
case Algorithm.RS256:
|
||||
case Algorithm.RS384:
|
||||
case Algorithm.RS512:
|
||||
case Algorithm.PS256:
|
||||
case Algorithm.PS384:
|
||||
case Algorithm.PS512:
|
||||
registerKeyProvider(realm, "algorithm", jwaAlgorithmName, GeneratedRsaKeyProviderFactory.ID, adminClient, testContext);
|
||||
break;
|
||||
case Algorithm.ES256:
|
||||
case Algorithm.ES384:
|
||||
case Algorithm.ES512:
|
||||
registerKeyProvider(realm, "ecdsaEllipticCurveKey", convertAlgorithmToECDomainParamNistRep(jwaAlgorithmName), GeneratedEcdsaKeyProviderFactory.ID, adminClient, testContext);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static String convertAlgorithmToECDomainParamNistRep(String algorithm) {
|
||||
switch(algorithm) {
|
||||
case Algorithm.ES256 :
|
||||
return "P-256";
|
||||
case Algorithm.ES384 :
|
||||
return "P-384";
|
||||
case Algorithm.ES512 :
|
||||
return "P-521";
|
||||
default :
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void registerKeyProvider(String realm, String providerSpecificKey, String providerSpecificValue, String providerId, Keycloak adminClient, TestContext testContext) {
|
||||
long priority = System.currentTimeMillis();
|
||||
|
||||
ComponentRepresentation rep = createKeyRep("valid", GeneratedEcdsaKeyProviderFactory.ID);
|
||||
ComponentRepresentation rep = createKeyRep("valid", providerId);
|
||||
rep.setConfig(new MultivaluedHashMap<>());
|
||||
rep.getConfig().putSingle("priority", Long.toString(priority));
|
||||
rep.getConfig().putSingle(ECDSA_ELLIPTIC_CURVE_KEY, ecNistRep);
|
||||
rep.getConfig().putSingle(providerSpecificKey, providerSpecificValue);
|
||||
|
||||
try (Response response = adminClient.realm(realm).components().add(rep)) {
|
||||
String id = ApiUtil.getCreatedId(response);
|
||||
|
|
|
@ -47,7 +47,6 @@ public class AdminSignatureAlgorithmTest extends AbstractKeycloakTest {
|
|||
|
||||
@Test
|
||||
public void changeRealmTokenAlgorithm() throws Exception {
|
||||
TokenSignatureUtil.registerKeyProvider("master", "P-256", adminClient, testContext);
|
||||
TokenSignatureUtil.changeRealmTokenSignatureProvider("master", adminClient, Algorithm.ES256);
|
||||
|
||||
try (Keycloak adminClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(), suiteContext.getAuthServerInfo().getContextRoot().toString())) {
|
||||
|
|
|
@ -73,7 +73,6 @@ public class InitialAccessTokenTest extends AbstractClientRegistrationTest {
|
|||
@Test
|
||||
public void createWithES256() throws JWSInputException, ClientRegistrationException {
|
||||
try {
|
||||
TokenSignatureUtil.registerKeyProvider("P-256", adminClient, testContext);
|
||||
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.ES256);
|
||||
|
||||
ClientInitialAccessPresentation response = resource.create(new ClientInitialAccessCreatePresentation());
|
||||
|
@ -82,7 +81,7 @@ public class InitialAccessTokenTest extends AbstractClientRegistrationTest {
|
|||
String token = response.getToken();
|
||||
|
||||
JWSHeader header = new JWSInput(token).getHeader();
|
||||
assertEquals("HS256", header.getAlgorithm().name());
|
||||
assertEquals(Algorithm.HS256, header.getAlgorithm().name());
|
||||
|
||||
ClientRepresentation rep = new ClientRepresentation();
|
||||
ClientRepresentation created = reg.create(rep);
|
||||
|
|
|
@ -206,33 +206,42 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest {
|
|||
|
||||
@Test
|
||||
public void testSignaturesRequired() throws Exception {
|
||||
OIDCClientRepresentation clientRep = createRep();
|
||||
clientRep.setUserinfoSignedResponseAlg(Algorithm.ES256.toString());
|
||||
clientRep.setRequestObjectSigningAlg(Algorithm.ES256.toString());
|
||||
OIDCClientRepresentation clientRep = null;
|
||||
OIDCClientRepresentation response = null;
|
||||
try {
|
||||
clientRep = createRep();
|
||||
clientRep.setUserinfoSignedResponseAlg(Algorithm.ES256.toString());
|
||||
clientRep.setRequestObjectSigningAlg(Algorithm.ES256.toString());
|
||||
|
||||
OIDCClientRepresentation response = reg.oidc().create(clientRep);
|
||||
Assert.assertEquals(Algorithm.ES256.toString(), response.getUserinfoSignedResponseAlg());
|
||||
Assert.assertEquals(Algorithm.ES256.toString(), response.getRequestObjectSigningAlg());
|
||||
Assert.assertNotNull(response.getClientSecret());
|
||||
response = reg.oidc().create(clientRep);
|
||||
Assert.assertEquals(Algorithm.ES256.toString(), response.getUserinfoSignedResponseAlg());
|
||||
Assert.assertEquals(Algorithm.ES256.toString(), response.getRequestObjectSigningAlg());
|
||||
Assert.assertNotNull(response.getClientSecret());
|
||||
|
||||
// Test Keycloak representation
|
||||
ClientRepresentation kcClient = getClient(response.getClientId());
|
||||
OIDCAdvancedConfigWrapper config = OIDCAdvancedConfigWrapper.fromClientRepresentation(kcClient);
|
||||
Assert.assertEquals(config.getUserInfoSignedResponseAlg(), Algorithm.ES256);
|
||||
Assert.assertEquals(config.getRequestObjectSignatureAlg(), Algorithm.ES256);
|
||||
// Test Keycloak representation
|
||||
ClientRepresentation kcClient = getClient(response.getClientId());
|
||||
OIDCAdvancedConfigWrapper config = OIDCAdvancedConfigWrapper.fromClientRepresentation(kcClient);
|
||||
Assert.assertEquals(config.getUserInfoSignedResponseAlg(), Algorithm.ES256);
|
||||
Assert.assertEquals(config.getRequestObjectSignatureAlg(), Algorithm.ES256);
|
||||
|
||||
// update (ES256 to RS256)
|
||||
clientRep.setUserinfoSignedResponseAlg(Algorithm.RS256.toString());
|
||||
clientRep.setRequestObjectSigningAlg(Algorithm.RS256.toString());
|
||||
response = reg.oidc().create(clientRep);
|
||||
Assert.assertEquals(Algorithm.RS256.toString(), response.getUserinfoSignedResponseAlg());
|
||||
Assert.assertEquals(Algorithm.RS256.toString(), response.getRequestObjectSigningAlg());
|
||||
// update (ES256 to PS256)
|
||||
clientRep.setUserinfoSignedResponseAlg(Algorithm.PS256.toString());
|
||||
clientRep.setRequestObjectSigningAlg(Algorithm.PS256.toString());
|
||||
response = reg.oidc().create(clientRep);
|
||||
Assert.assertEquals(Algorithm.PS256.toString(), response.getUserinfoSignedResponseAlg());
|
||||
Assert.assertEquals(Algorithm.PS256.toString(), response.getRequestObjectSigningAlg());
|
||||
|
||||
// keycloak representation
|
||||
kcClient = getClient(response.getClientId());
|
||||
config = OIDCAdvancedConfigWrapper.fromClientRepresentation(kcClient);
|
||||
Assert.assertEquals(config.getUserInfoSignedResponseAlg(), Algorithm.RS256);
|
||||
Assert.assertEquals(config.getRequestObjectSignatureAlg(), Algorithm.RS256);
|
||||
// keycloak representation
|
||||
kcClient = getClient(response.getClientId());
|
||||
config = OIDCAdvancedConfigWrapper.fromClientRepresentation(kcClient);
|
||||
Assert.assertEquals(config.getUserInfoSignedResponseAlg(), Algorithm.PS256);
|
||||
Assert.assertEquals(config.getRequestObjectSignatureAlg(), Algorithm.PS256);
|
||||
} finally {
|
||||
// back to RS256 for other tests
|
||||
clientRep.setUserinfoSignedResponseAlg(Algorithm.RS256.toString());
|
||||
clientRep.setRequestObjectSigningAlg(Algorithm.RS256.toString());
|
||||
response = reg.oidc().create(clientRep);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -379,7 +379,6 @@ public class LoginTest extends AbstractTestRealmKeycloakTest {
|
|||
assertEquals("HS256", algorithm);
|
||||
|
||||
try {
|
||||
TokenSignatureUtil.registerKeyProvider("P-256", adminClient, testContext);
|
||||
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.ES256);
|
||||
|
||||
oauth.openLoginForm();
|
||||
|
|
|
@ -96,6 +96,9 @@ public class FallbackKeyProviderTest extends AbstractKeycloakTest {
|
|||
String[] algorithmsToTest = new String[] {
|
||||
Algorithm.RS384,
|
||||
Algorithm.RS512,
|
||||
Algorithm.PS256,
|
||||
Algorithm.PS384,
|
||||
Algorithm.PS512,
|
||||
Algorithm.ES256,
|
||||
Algorithm.ES384,
|
||||
Algorithm.ES512
|
||||
|
|
|
@ -1057,66 +1057,6 @@ public class AccessTokenTest extends AbstractKeycloakTest {
|
|||
.post(Entity.form(form));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void accessTokenRequest_RealmRS256_ClientRS384_EffectiveRS384() throws Exception {
|
||||
try {
|
||||
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.RS256);
|
||||
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), Algorithm.RS384);
|
||||
tokenRequest(Algorithm.HS256, Algorithm.RS384, Algorithm.RS256);
|
||||
} finally {
|
||||
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), Algorithm.RS256);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void accessTokenRequest_RealmRS512_ClientRS512_EffectiveRS512() throws Exception {
|
||||
try {
|
||||
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.RS512);
|
||||
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), Algorithm.RS512);
|
||||
tokenRequest(Algorithm.HS256, Algorithm.RS512, Algorithm.RS512);
|
||||
} finally {
|
||||
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.RS256);
|
||||
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), Algorithm.RS256);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void accessTokenRequest_RealmRS256_ClientES256_EffectiveES256() throws Exception {
|
||||
try {
|
||||
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.RS256);
|
||||
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), Algorithm.ES256);
|
||||
TokenSignatureUtil.registerKeyProvider("P-256", adminClient, testContext);
|
||||
tokenRequestSignatureVerifyOnly(Algorithm.HS256, Algorithm.ES256, Algorithm.RS256);
|
||||
} finally {
|
||||
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), Algorithm.RS256);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void accessTokenRequest_RealmES384_ClientES384_EffectiveES384() throws Exception {
|
||||
try {
|
||||
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.ES384);
|
||||
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), Algorithm.ES384);
|
||||
TokenSignatureUtil.registerKeyProvider("P-384", adminClient, testContext);
|
||||
tokenRequestSignatureVerifyOnly(Algorithm.HS256, Algorithm.ES384, Algorithm.ES384);
|
||||
} finally {
|
||||
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.RS256);
|
||||
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), Algorithm.RS256);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void accessTokenRequest_RealmRS256_ClientES512_EffectiveES512() throws Exception {
|
||||
try {
|
||||
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.RS256);
|
||||
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), Algorithm.ES512);
|
||||
TokenSignatureUtil.registerKeyProvider("P-521", adminClient, testContext);
|
||||
tokenRequestSignatureVerifyOnly(Algorithm.HS256, Algorithm.ES512, Algorithm.RS256);
|
||||
} finally {
|
||||
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), Algorithm.RS256);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clientAccessTokenLifespanOverride() {
|
||||
ClientResource client = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app");
|
||||
|
@ -1161,6 +1101,59 @@ public class AccessTokenTest extends AbstractKeycloakTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void accessTokenRequest_ClientPS384_RealmRS256() throws Exception {
|
||||
conductAccessTokenRequest(Algorithm.HS256, Algorithm.PS384, Algorithm.RS256);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void accessTokenRequest_ClientPS256_RealmPS256() throws Exception {
|
||||
conductAccessTokenRequest(Algorithm.HS256, Algorithm.PS256, Algorithm.PS256);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void accessTokenRequest_ClientPS512_RealmPS256() throws Exception {
|
||||
conductAccessTokenRequest(Algorithm.HS256, Algorithm.PS512, Algorithm.PS256);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void accessTokenRequest_ClientRS384_RealmRS256() throws Exception {
|
||||
conductAccessTokenRequest(Algorithm.HS256, Algorithm.RS384, Algorithm.RS256);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void accessTokenRequest_ClientRS512_RealmRS512() throws Exception {
|
||||
conductAccessTokenRequest(Algorithm.HS256, Algorithm.RS512, Algorithm.RS512);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void accessTokenRequest_ClientES256_RealmPS256() throws Exception {
|
||||
conductAccessTokenRequest(Algorithm.HS256, Algorithm.ES256, Algorithm.PS256);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void accessTokenRequest_ClientES384_RealmES384() throws Exception {
|
||||
conductAccessTokenRequest(Algorithm.HS256, Algorithm.ES384, Algorithm.ES384);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void accessTokenRequest_ClientES512_RealmRS256() throws Exception {
|
||||
conductAccessTokenRequest(Algorithm.HS256, Algorithm.ES512, Algorithm.RS256);
|
||||
}
|
||||
|
||||
private void conductAccessTokenRequest(String expectedRefreshAlg, String expectedAccessAlg, String expectedIdTokenAlg) throws Exception {
|
||||
try {
|
||||
/// Realm Setting is used for ID Token Signature Algorithm
|
||||
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, expectedIdTokenAlg);
|
||||
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), expectedAccessAlg);
|
||||
tokenRequest(expectedRefreshAlg, expectedAccessAlg, expectedIdTokenAlg);
|
||||
} finally {
|
||||
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.RS256);
|
||||
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), Algorithm.RS256);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
private void tokenRequest(String expectedRefreshAlg, String expectedAccessAlg, String expectedIdTokenAlg) throws Exception {
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
|
||||
|
@ -1204,33 +1197,4 @@ public class AccessTokenTest extends AbstractKeycloakTest {
|
|||
assertEquals(sessionId, token.getSessionState());
|
||||
}
|
||||
|
||||
private void tokenRequestSignatureVerifyOnly(String expectedRefreshAlg, String expectedAccessAlg, String expectedIdTokenAlg) throws Exception {
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
|
||||
|
||||
assertEquals(200, response.getStatusCode());
|
||||
|
||||
assertEquals("bearer", response.getTokenType());
|
||||
|
||||
JWSHeader header = new JWSInput(response.getAccessToken()).getHeader();
|
||||
assertEquals(expectedAccessAlg, header.getAlgorithm().name());
|
||||
assertEquals("JWT", header.getType());
|
||||
assertNull(header.getContentType());
|
||||
|
||||
header = new JWSInput(response.getIdToken()).getHeader();
|
||||
assertEquals(expectedIdTokenAlg, header.getAlgorithm().name());
|
||||
assertEquals("JWT", header.getType());
|
||||
assertNull(header.getContentType());
|
||||
|
||||
header = new JWSInput(response.getRefreshToken()).getHeader();
|
||||
assertEquals(expectedRefreshAlg, header.getAlgorithm().name());
|
||||
assertEquals("JWT", header.getType());
|
||||
assertNull(header.getContentType());
|
||||
|
||||
assertEquals(TokenSignatureUtil.verifySignature(expectedAccessAlg, response.getAccessToken(), adminClient), true);
|
||||
assertEquals(TokenSignatureUtil.verifySignature(expectedIdTokenAlg, response.getIdToken(), adminClient), true);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ import org.keycloak.admin.client.resource.RealmResource;
|
|||
import org.keycloak.admin.client.resource.RoleResource;
|
||||
import org.keycloak.admin.client.resource.UserResource;
|
||||
import org.keycloak.common.constants.ServiceAccountConstants;
|
||||
import org.keycloak.crypto.Algorithm;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.jose.jws.JWSHeader;
|
||||
|
@ -695,6 +696,28 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
|
|||
testOfflineSessionExpiration(IDLE_LIFESPAN, MAX_LIFESPAN, IDLE_LIFESPAN + SessionTimeoutHelper.IDLE_TIMEOUT_WINDOW_SECONDS + 60);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void offlineTokenRequest_ClientES256_RealmPS256() throws Exception {
|
||||
conductOfflineTokenRequest(Algorithm.HS256, Algorithm.ES256, Algorithm.PS256);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void offlineTokenRequest_ClientPS256_RealmES256() throws Exception {
|
||||
conductOfflineTokenRequest(Algorithm.HS256, Algorithm.PS256, Algorithm.ES256);
|
||||
}
|
||||
|
||||
private void conductOfflineTokenRequest(String expectedRefreshAlg, String expectedAccessAlg, String expectedIdTokenAlg) throws Exception {
|
||||
try {
|
||||
/// Realm Setting is used for ID Token Signature Algorithm
|
||||
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, expectedIdTokenAlg);
|
||||
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "offline-client"), expectedAccessAlg);
|
||||
offlineTokenRequest(expectedRefreshAlg, expectedAccessAlg, expectedIdTokenAlg);
|
||||
} finally {
|
||||
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.RS256);
|
||||
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "offline-client"), Algorithm.RS256);
|
||||
}
|
||||
}
|
||||
|
||||
private void testOfflineSessionExpiration(int idleTime, int maxLifespan, int offset) {
|
||||
int prev[] = null;
|
||||
try {
|
||||
|
@ -821,15 +844,4 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void offlineTokenRequest_RealmRS512_ClientRS384() throws Exception {
|
||||
try {
|
||||
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, "RS512");
|
||||
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "offline-client"), "RS384");
|
||||
offlineTokenRequest("HS256","RS384", "RS512");
|
||||
} finally {
|
||||
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, "RS256");
|
||||
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "offline-client"), "RS256");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -871,6 +871,46 @@ public class RefreshTokenTest extends AbstractKeycloakTest {
|
|||
assertNotNull(response.getRefreshToken());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tokenRefreshRequest_ClientRS384_RealmRS384() throws Exception {
|
||||
conductTokenRefreshRequest(Algorithm.HS256, Algorithm.RS384, Algorithm.RS384);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tokenRefreshRequest_ClientRS512_RealmRS256() throws Exception {
|
||||
conductTokenRefreshRequest(Algorithm.HS256, Algorithm.RS512, Algorithm.RS256);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tokenRefreshRequest_ClientES256_RealmRS256() throws Exception {
|
||||
conductTokenRefreshRequest(Algorithm.HS256, Algorithm.ES256, Algorithm.RS256);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tokenRefreshRequest_ClientES384_RealmES384() throws Exception {
|
||||
conductTokenRefreshRequest(Algorithm.HS256, Algorithm.ES384, Algorithm.ES384);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tokenRefreshRequest_ClientES512_RealmRS256() throws Exception {
|
||||
conductTokenRefreshRequest(Algorithm.HS256, Algorithm.ES512, Algorithm.RS256);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tokenRefreshRequest_ClientPS256_RealmRS256() throws Exception {
|
||||
conductTokenRefreshRequest(Algorithm.HS256, Algorithm.PS256, Algorithm.RS256);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tokenRefreshRequest_ClientPS384_RealmES384() throws Exception {
|
||||
conductTokenRefreshRequest(Algorithm.HS256, Algorithm.PS384, Algorithm.ES384);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tokenRefreshRequest_ClientPS512_RealmPS256() throws Exception {
|
||||
conductTokenRefreshRequest(Algorithm.HS256, Algorithm.PS512, Algorithm.PS256);
|
||||
}
|
||||
|
||||
protected Response executeRefreshToken(WebTarget refreshTarget, String refreshToken) {
|
||||
String header = BasicAuthHelper.createHeader("test-app", "password");
|
||||
Form form = new Form();
|
||||
|
@ -892,6 +932,18 @@ public class RefreshTokenTest extends AbstractKeycloakTest {
|
|||
.post(Entity.form(form));
|
||||
}
|
||||
|
||||
private void conductTokenRefreshRequest(String expectedRefreshAlg, String expectedAccessAlg, String expectedIdTokenAlg) throws Exception {
|
||||
try {
|
||||
// Realm setting is used for ID Token signature algorithm
|
||||
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, expectedIdTokenAlg);
|
||||
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), expectedAccessAlg);
|
||||
refreshToken(expectedRefreshAlg, expectedAccessAlg, expectedIdTokenAlg);
|
||||
} finally {
|
||||
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.RS256);
|
||||
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), Algorithm.RS256);
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshToken(String expectedRefreshAlg, String expectedAccessAlg, String expectedIdTokenAlg) throws Exception {
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
|
||||
|
@ -957,118 +1009,4 @@ public class RefreshTokenTest extends AbstractKeycloakTest {
|
|||
setTimeOffset(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tokenRefreshRequest_RealmRS384_ClientRS384_EffectiveRS384() throws Exception {
|
||||
try {
|
||||
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.RS384);
|
||||
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), Algorithm.RS384);
|
||||
refreshToken(Algorithm.HS256, Algorithm.RS384, Algorithm.RS384);
|
||||
} finally {
|
||||
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.RS256);
|
||||
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), Algorithm.RS256);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tokenRefreshRequest_RealmRS256_ClientRS512_EffectiveRS256() throws Exception {
|
||||
try {
|
||||
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.RS256);
|
||||
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), Algorithm.RS512);
|
||||
refreshToken(Algorithm.HS256, Algorithm.RS512, Algorithm.RS256);
|
||||
} finally {
|
||||
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), Algorithm.RS256);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tokenRefreshRequest_RealmRS256_ClientES256_EffectiveRS256() throws Exception {
|
||||
try {
|
||||
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.RS256);
|
||||
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), Algorithm.ES256);
|
||||
TokenSignatureUtil.registerKeyProvider("P-256", adminClient, testContext);
|
||||
refreshTokenSignatureVerifyOnly(Algorithm.HS256, Algorithm.ES256, Algorithm.RS256);
|
||||
} finally {
|
||||
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), Algorithm.RS256);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tokenRefreshRequest_RealmES384_ClientES384_EffectiveES384() throws Exception {
|
||||
try {
|
||||
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.ES384);
|
||||
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), Algorithm.ES384);
|
||||
TokenSignatureUtil.registerKeyProvider("P-384", adminClient, testContext);
|
||||
refreshTokenSignatureVerifyOnly(Algorithm.HS256, Algorithm.ES384, Algorithm.ES384);
|
||||
} finally {
|
||||
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.RS256);
|
||||
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), Algorithm.RS256);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tokenRefreshRequest_RealmRS256_ClientES512_EffectiveRS256() throws Exception {
|
||||
try {
|
||||
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.RS256);
|
||||
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), Algorithm.ES512);
|
||||
TokenSignatureUtil.registerKeyProvider("P-521", adminClient, testContext);
|
||||
refreshTokenSignatureVerifyOnly(Algorithm.HS256, Algorithm.ES512, Algorithm.RS256);
|
||||
} finally {
|
||||
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), Algorithm.RS256);
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshTokenSignatureVerifyOnly(String expectedRefreshAlg, String expectedAccessAlg, String expectedIdTokenAlg) throws Exception {
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
|
||||
EventRepresentation loginEvent = events.expectLogin().assertEvent();
|
||||
|
||||
String sessionId = loginEvent.getSessionId();
|
||||
String codeId = loginEvent.getDetails().get(Details.CODE_ID);
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
|
||||
OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
|
||||
|
||||
JWSHeader header = new JWSInput(tokenResponse.getAccessToken()).getHeader();
|
||||
assertEquals(expectedAccessAlg, header.getAlgorithm().name());
|
||||
assertEquals("JWT", header.getType());
|
||||
assertNull(header.getContentType());
|
||||
|
||||
header = new JWSInput(tokenResponse.getIdToken()).getHeader();
|
||||
assertEquals(expectedIdTokenAlg, header.getAlgorithm().name());
|
||||
assertEquals("JWT", header.getType());
|
||||
assertNull(header.getContentType());
|
||||
|
||||
header = new JWSInput(tokenResponse.getRefreshToken()).getHeader();
|
||||
assertEquals(expectedRefreshAlg, header.getAlgorithm().name());
|
||||
assertEquals("JWT", header.getType());
|
||||
assertNull(header.getContentType());
|
||||
|
||||
String refreshTokenString = tokenResponse.getRefreshToken();
|
||||
|
||||
EventRepresentation tokenEvent = events.expectCodeToToken(codeId, sessionId).assertEvent();
|
||||
|
||||
assertNotNull(refreshTokenString);
|
||||
|
||||
assertEquals("bearer", tokenResponse.getTokenType());
|
||||
|
||||
setTimeOffset(2);
|
||||
|
||||
OAuthClient.AccessTokenResponse response = oauth.doRefreshTokenRequest(refreshTokenString, "password");
|
||||
|
||||
assertEquals(200, response.getStatusCode());
|
||||
|
||||
assertEquals("bearer", response.getTokenType());
|
||||
|
||||
// decode JWS for refreshed access token and refresh token
|
||||
assertEquals(TokenSignatureUtil.verifySignature(expectedAccessAlg, response.getAccessToken(), adminClient), true);
|
||||
assertEquals(TokenSignatureUtil.verifySignature(expectedIdTokenAlg, response.getIdToken(), adminClient), true);
|
||||
|
||||
EventRepresentation refreshEvent = events.expectRefresh(tokenEvent.getDetails().get(Details.REFRESH_TOKEN_ID), sessionId).assertEvent();
|
||||
Assert.assertNotEquals(tokenEvent.getDetails().get(Details.TOKEN_ID), refreshEvent.getDetails().get(Details.TOKEN_ID));
|
||||
Assert.assertNotEquals(tokenEvent.getDetails().get(Details.REFRESH_TOKEN_ID), refreshEvent.getDetails().get(Details.UPDATED_REFRESH_TOKEN_ID));
|
||||
|
||||
setTimeOffset(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -26,8 +26,11 @@ import org.junit.Test;
|
|||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
|
||||
import org.keycloak.crypto.Algorithm;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.jose.jws.JWSHeader;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.models.utils.TimeBasedOTP;
|
||||
|
@ -39,17 +42,20 @@ import org.keycloak.representations.idm.UserRepresentation;
|
|||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||
import org.keycloak.testsuite.Assert;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.util.ClientBuilder;
|
||||
import org.keycloak.testsuite.util.ClientManager;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
import org.keycloak.testsuite.util.RealmBuilder;
|
||||
import org.keycloak.testsuite.util.RealmManager;
|
||||
import org.keycloak.testsuite.util.TokenSignatureUtil;
|
||||
import org.keycloak.testsuite.util.UserBuilder;
|
||||
import org.keycloak.testsuite.util.UserManager;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
|
@ -221,6 +227,79 @@ public class ResourceOwnerPasswordCredentialsGrantTest extends AbstractKeycloakT
|
|||
events.expectRefresh(refreshToken.getId(), refreshToken.getSessionState()).user(userId).client(clientId).assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void grantRequest_ClientES256_RealmPS256() throws Exception {
|
||||
conductGrantRequest(Algorithm.HS256, Algorithm.ES256, Algorithm.PS256);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void grantRequest_ClientPS256_RealmES256() throws Exception {
|
||||
conductGrantRequest(Algorithm.HS256, Algorithm.PS256, Algorithm.ES256);
|
||||
}
|
||||
|
||||
private void conductGrantRequest(String expectedRefreshAlg, String expectedAccessAlg, String realmTokenAlg) throws Exception {
|
||||
try {
|
||||
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, realmTokenAlg);
|
||||
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "resource-owner"), expectedAccessAlg);
|
||||
grantRequest(expectedRefreshAlg, expectedAccessAlg);
|
||||
} finally {
|
||||
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.RS256);
|
||||
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "resource-owner"), Algorithm.RS256);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
private void grantRequest(String expectedRefreshAlg, String expectedAccessAlg) throws Exception {
|
||||
String clientId = "resource-owner";
|
||||
String login = "direct-login";
|
||||
|
||||
oauth.clientId(clientId);
|
||||
|
||||
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", login, "password", null);
|
||||
|
||||
assertEquals(200, response.getStatusCode());
|
||||
|
||||
AccessToken accessToken = oauth.verifyToken(response.getAccessToken());
|
||||
RefreshToken refreshToken = oauth.parseRefreshToken(response.getRefreshToken());
|
||||
JWSHeader header = new JWSInput(response.getAccessToken()).getHeader();
|
||||
|
||||
assertEquals(expectedAccessAlg, header.getAlgorithm().name());
|
||||
assertEquals("JWT", header.getType());
|
||||
assertNull(header.getContentType());
|
||||
|
||||
header = new JWSInput(response.getRefreshToken()).getHeader();
|
||||
assertEquals(expectedRefreshAlg, header.getAlgorithm().name());
|
||||
assertEquals("JWT", header.getType());
|
||||
assertNull(header.getContentType());
|
||||
|
||||
events.expectLogin()
|
||||
.client(clientId)
|
||||
.user(userId)
|
||||
.session(accessToken.getSessionState())
|
||||
.detail(Details.GRANT_TYPE, OAuth2Constants.PASSWORD)
|
||||
.detail(Details.TOKEN_ID, accessToken.getId())
|
||||
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
|
||||
.detail(Details.USERNAME, login)
|
||||
.removeDetail(Details.CODE_ID)
|
||||
.removeDetail(Details.REDIRECT_URI)
|
||||
.removeDetail(Details.CONSENT)
|
||||
.assertEvent();
|
||||
|
||||
Assert.assertTrue(login.equals(accessToken.getPreferredUsername()) || login.equals(accessToken.getEmail()));
|
||||
|
||||
assertEquals(accessToken.getSessionState(), refreshToken.getSessionState());
|
||||
|
||||
OAuthClient.AccessTokenResponse refreshedResponse = oauth.doRefreshTokenRequest(response.getRefreshToken(), "secret");
|
||||
|
||||
AccessToken refreshedAccessToken = oauth.verifyToken(refreshedResponse.getAccessToken());
|
||||
RefreshToken refreshedRefreshToken = oauth.parseRefreshToken(refreshedResponse.getRefreshToken());
|
||||
|
||||
assertEquals(accessToken.getSessionState(), refreshedAccessToken.getSessionState());
|
||||
assertEquals(accessToken.getSessionState(), refreshedRefreshToken.getSessionState());
|
||||
|
||||
events.expectRefresh(refreshToken.getId(), refreshToken.getSessionState()).user(userId).client(clientId).assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void grantAccessTokenLogout() throws Exception {
|
||||
oauth.clientId("resource-owner");
|
||||
|
|
|
@ -23,8 +23,11 @@ import org.junit.Rule;
|
|||
import org.junit.Test;
|
||||
import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
|
||||
import org.keycloak.common.constants.ServiceAccountConstants;
|
||||
import org.keycloak.crypto.Algorithm;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.jose.jws.JWSHeader;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.RefreshToken;
|
||||
|
@ -33,15 +36,18 @@ import org.keycloak.representations.idm.RealmRepresentation;
|
|||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.util.ClientBuilder;
|
||||
import org.keycloak.testsuite.util.ClientManager;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
import org.keycloak.testsuite.util.RealmBuilder;
|
||||
import org.keycloak.testsuite.util.TokenSignatureUtil;
|
||||
import org.keycloak.testsuite.util.UserBuilder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
|
@ -269,4 +275,68 @@ public class ServiceAccountTest extends AbstractKeycloakTest {
|
|||
userName = user.getUsername();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clientCredentialsAuthRequest_ClientES256_RealmPS256() throws Exception {
|
||||
conductClientCredentialsAuthRequest(Algorithm.HS256, Algorithm.ES256, Algorithm.PS256);
|
||||
}
|
||||
|
||||
private void conductClientCredentialsAuthRequest(String expectedRefreshAlg, String expectedAccessAlg, String realmTokenAlg) throws Exception {
|
||||
try {
|
||||
/// Realm Setting is used for ID Token Signature Algorithm
|
||||
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, realmTokenAlg);
|
||||
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "service-account-cl"), expectedAccessAlg);
|
||||
clientCredentialsAuthSuccess(expectedRefreshAlg, expectedAccessAlg);
|
||||
} finally {
|
||||
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.RS256);
|
||||
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "service-account-cl"), Algorithm.RS256);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
private void clientCredentialsAuthSuccess(String expectedRefreshAlg, String expectedAccessAlg) throws Exception {
|
||||
oauth.clientId("service-account-cl");
|
||||
|
||||
OAuthClient.AccessTokenResponse response = oauth.doClientCredentialsGrantAccessTokenRequest("secret1");
|
||||
|
||||
assertEquals(200, response.getStatusCode());
|
||||
|
||||
AccessToken accessToken = oauth.verifyToken(response.getAccessToken());
|
||||
RefreshToken refreshToken = oauth.parseRefreshToken(response.getRefreshToken());
|
||||
|
||||
JWSHeader header = new JWSInput(response.getAccessToken()).getHeader();
|
||||
assertEquals(expectedAccessAlg, header.getAlgorithm().name());
|
||||
assertEquals("JWT", header.getType());
|
||||
assertNull(header.getContentType());
|
||||
|
||||
header = new JWSInput(response.getRefreshToken()).getHeader();
|
||||
assertEquals(expectedRefreshAlg, header.getAlgorithm().name());
|
||||
assertEquals("JWT", header.getType());
|
||||
assertNull(header.getContentType());
|
||||
|
||||
events.expectClientLogin()
|
||||
.client("service-account-cl")
|
||||
.user(userId)
|
||||
.session(accessToken.getSessionState())
|
||||
.detail(Details.TOKEN_ID, accessToken.getId())
|
||||
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
|
||||
.detail(Details.USERNAME, userName)
|
||||
.assertEvent();
|
||||
|
||||
assertEquals(accessToken.getSessionState(), refreshToken.getSessionState());
|
||||
System.out.println("Access token other claims: " + accessToken.getOtherClaims());
|
||||
Assert.assertEquals("service-account-cl", accessToken.getOtherClaims().get(ServiceAccountConstants.CLIENT_ID));
|
||||
Assert.assertTrue(accessToken.getOtherClaims().containsKey(ServiceAccountConstants.CLIENT_ADDRESS));
|
||||
Assert.assertTrue(accessToken.getOtherClaims().containsKey(ServiceAccountConstants.CLIENT_HOST));
|
||||
|
||||
OAuthClient.AccessTokenResponse refreshedResponse = oauth.doRefreshTokenRequest(response.getRefreshToken(), "secret1");
|
||||
|
||||
AccessToken refreshedAccessToken = oauth.verifyToken(refreshedResponse.getAccessToken());
|
||||
RefreshToken refreshedRefreshToken = oauth.parseRefreshToken(refreshedResponse.getRefreshToken());
|
||||
|
||||
assertEquals(accessToken.getSessionState(), refreshedAccessToken.getSessionState());
|
||||
assertEquals(accessToken.getSessionState(), refreshedRefreshToken.getSessionState());
|
||||
|
||||
events.expectRefresh(refreshToken.getId(), refreshToken.getSessionState()).user(userId).client("service-account-cl").assertEvent();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ package org.keycloak.testsuite.oauth;
|
|||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.TextNode;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
@ -249,16 +248,24 @@ public class TokenIntrospectionTest extends AbstractTestRealmKeycloakTest {
|
|||
|
||||
@Test
|
||||
public void testIntrospectAccessTokenES256() throws Exception {
|
||||
testIntrospectAccessToken(Algorithm.ES256);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntrospectAccessTokenPS256() throws Exception {
|
||||
testIntrospectAccessToken(Algorithm.PS256);
|
||||
}
|
||||
|
||||
private void testIntrospectAccessToken(String jwaAlgorithm) throws Exception {
|
||||
try {
|
||||
TokenSignatureUtil.registerKeyProvider("P-256", adminClient, testContext);
|
||||
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), Algorithm.ES256);
|
||||
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), jwaAlgorithm);
|
||||
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
EventRepresentation loginEvent = events.expectLogin().assertEvent();
|
||||
AccessTokenResponse accessTokenResponse = oauth.doAccessTokenRequest(code, "password");
|
||||
|
||||
assertEquals("ES256", new JWSInput(accessTokenResponse.getAccessToken()).getHeader().getAlgorithm().name());
|
||||
assertEquals(jwaAlgorithm, new JWSInput(accessTokenResponse.getAccessToken()).getHeader().getAlgorithm().name());
|
||||
|
||||
String tokenResponse = oauth.introspectAccessTokenWithClientCredential("confidential-cli", "secret1", accessTokenResponse.getAccessToken());
|
||||
|
||||
|
|
|
@ -930,6 +930,24 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
|
|||
requestUriParamSignedIn(Algorithm.RS512, Algorithm.RS512);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestUriParamSignedExpectedPS256ActualPS256() throws Exception {
|
||||
// will success
|
||||
requestUriParamSignedIn(Algorithm.PS256, Algorithm.PS256);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestUriParamSignedExpectedPS384ActualPS384() throws Exception {
|
||||
// will success
|
||||
requestUriParamSignedIn(Algorithm.PS384, Algorithm.PS384);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestUriParamSignedExpectedPS512ActualPS512() throws Exception {
|
||||
// will success
|
||||
requestUriParamSignedIn(Algorithm.PS512, Algorithm.PS512);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestUriParamSignedExpectedAnyActualES256() throws Exception {
|
||||
// Algorithm is null if 'any'
|
||||
|
|
|
@ -124,9 +124,11 @@ public class OIDCWellKnownProviderTest extends AbstractKeycloakTest {
|
|||
assertContains(oidcConfig.getResponseModesSupported(), "query", "fragment");
|
||||
|
||||
Assert.assertNames(oidcConfig.getSubjectTypesSupported(), "pairwise", "public");
|
||||
Assert.assertNames(oidcConfig.getIdTokenSigningAlgValuesSupported(), Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512);
|
||||
Assert.assertNames(oidcConfig.getUserInfoSigningAlgValuesSupported(), "none", Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512);
|
||||
Assert.assertNames(oidcConfig.getRequestObjectSigningAlgValuesSupported(), "none", Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512);
|
||||
|
||||
// Signature algorithms
|
||||
Assert.assertNames(oidcConfig.getIdTokenSigningAlgValuesSupported(), Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512, Algorithm.PS256, Algorithm.PS384, Algorithm.PS512);
|
||||
Assert.assertNames(oidcConfig.getUserInfoSigningAlgValuesSupported(), "none", Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512, Algorithm.PS256, Algorithm.PS384, Algorithm.PS512);
|
||||
Assert.assertNames(oidcConfig.getRequestObjectSigningAlgValuesSupported(), "none", Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.PS256, Algorithm.PS384, Algorithm.PS512);
|
||||
|
||||
// Client authentication
|
||||
Assert.assertNames(oidcConfig.getTokenEndpointAuthMethodsSupported(), "client_secret_basic", "client_secret_post", "private_key_jwt", "client_secret_jwt");
|
||||
|
@ -194,7 +196,7 @@ public class OIDCWellKnownProviderTest extends AbstractKeycloakTest {
|
|||
|
||||
@Test
|
||||
public void certs() throws IOException {
|
||||
TokenSignatureUtil.registerKeyProvider("P-256", adminClient, testContext);
|
||||
TokenSignatureUtil.registerKeyProvider(Algorithm.ES256, adminClient, testContext);
|
||||
|
||||
OIDCConfigurationRepresentation representation = SimpleHttp.doGet(getAuthServerRoot().toString() + "realms/test/.well-known/openid-configuration", client).asJson(OIDCConfigurationRepresentation.class);
|
||||
String jwksUri = representation.getJwksUri();
|
||||
|
|
|
@ -280,62 +280,12 @@ public class UserInfoTest extends AbstractKeycloakTest {
|
|||
|
||||
@Test
|
||||
public void testSuccessSignedResponseES256() throws Exception {
|
||||
testSuccessSignedResponse(Algorithm.ES256);
|
||||
}
|
||||
|
||||
try {
|
||||
TokenSignatureUtil.registerKeyProvider("P-256", adminClient, testContext);
|
||||
|
||||
// Require signed userInfo request
|
||||
ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app");
|
||||
ClientRepresentation clientRep = clientResource.toRepresentation();
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setUserInfoSignedResponseAlg(Algorithm.ES256);
|
||||
clientResource.update(clientRep);
|
||||
|
||||
// test signed response
|
||||
Client client = ClientBuilder.newClient();
|
||||
|
||||
try {
|
||||
AccessTokenResponse accessTokenResponse = executeGrantAccessTokenRequest(client);
|
||||
|
||||
Response response = UserInfoClientUtil.executeUserInfoRequest_getMethod(client, accessTokenResponse.getToken());
|
||||
|
||||
events.expect(EventType.USER_INFO_REQUEST)
|
||||
.session(Matchers.notNullValue(String.class))
|
||||
.detail(Details.AUTH_METHOD, Details.VALIDATE_ACCESS_TOKEN)
|
||||
.detail(Details.USERNAME, "test-user@localhost")
|
||||
.detail(Details.SIGNATURE_REQUIRED, "true")
|
||||
.detail(Details.SIGNATURE_ALGORITHM, Algorithm.ES256.toString())
|
||||
.assertEvent();
|
||||
|
||||
Assert.assertEquals(200, response.getStatus());
|
||||
Assert.assertEquals(response.getHeaderString(HttpHeaders.CONTENT_TYPE), MediaType.APPLICATION_JWT);
|
||||
String signedResponse = response.readEntity(String.class);
|
||||
response.close();
|
||||
|
||||
JWSInput jwsInput = new JWSInput(signedResponse);
|
||||
|
||||
assertEquals("ES256", jwsInput.getHeader().getAlgorithm().name());
|
||||
|
||||
UserInfo userInfo = JsonSerialization.readValue(jwsInput.getContent(), UserInfo.class);
|
||||
|
||||
Assert.assertNotNull(userInfo);
|
||||
Assert.assertNotNull(userInfo.getSubject());
|
||||
Assert.assertEquals("test-user@localhost", userInfo.getEmail());
|
||||
Assert.assertEquals("test-user@localhost", userInfo.getPreferredUsername());
|
||||
|
||||
Assert.assertTrue(userInfo.hasAudience("test-app"));
|
||||
String expectedIssuer = Urls.realmIssuer(new URI(AUTH_SERVER_ROOT), "test");
|
||||
Assert.assertEquals(expectedIssuer, userInfo.getIssuer());
|
||||
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
|
||||
// Revert signed userInfo request
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setUserInfoSignedResponseAlg(null);
|
||||
clientResource.update(clientRep);
|
||||
} finally {
|
||||
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, org.keycloak.crypto.Algorithm.RS256);
|
||||
}
|
||||
@Test
|
||||
public void testSuccessSignedResponsePS256() throws Exception {
|
||||
testSuccessSignedResponse(Algorithm.PS256);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -510,4 +460,62 @@ public class UserInfoTest extends AbstractKeycloakTest {
|
|||
.assertEvent();
|
||||
UserInfoClientUtil.testSuccessfulUserInfoResponse(response, "test-user@localhost", "test-user@localhost");
|
||||
}
|
||||
|
||||
private void testSuccessSignedResponse(Algorithm sigAlg) throws Exception {
|
||||
|
||||
try {
|
||||
// Require signed userInfo request
|
||||
ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app");
|
||||
ClientRepresentation clientRep = clientResource.toRepresentation();
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setUserInfoSignedResponseAlg(sigAlg);
|
||||
clientResource.update(clientRep);
|
||||
|
||||
// test signed response
|
||||
Client client = ClientBuilder.newClient();
|
||||
|
||||
try {
|
||||
AccessTokenResponse accessTokenResponse = executeGrantAccessTokenRequest(client);
|
||||
|
||||
Response response = UserInfoClientUtil.executeUserInfoRequest_getMethod(client, accessTokenResponse.getToken());
|
||||
|
||||
events.expect(EventType.USER_INFO_REQUEST)
|
||||
.session(Matchers.notNullValue(String.class))
|
||||
.detail(Details.AUTH_METHOD, Details.VALIDATE_ACCESS_TOKEN)
|
||||
.detail(Details.USERNAME, "test-user@localhost")
|
||||
.detail(Details.SIGNATURE_REQUIRED, "true")
|
||||
.detail(Details.SIGNATURE_ALGORITHM, sigAlg.toString())
|
||||
.assertEvent();
|
||||
|
||||
Assert.assertEquals(200, response.getStatus());
|
||||
Assert.assertEquals(response.getHeaderString(HttpHeaders.CONTENT_TYPE), MediaType.APPLICATION_JWT);
|
||||
String signedResponse = response.readEntity(String.class);
|
||||
response.close();
|
||||
|
||||
JWSInput jwsInput = new JWSInput(signedResponse);
|
||||
|
||||
assertEquals(sigAlg.toString(), jwsInput.getHeader().getAlgorithm().name());
|
||||
|
||||
UserInfo userInfo = JsonSerialization.readValue(jwsInput.getContent(), UserInfo.class);
|
||||
|
||||
Assert.assertNotNull(userInfo);
|
||||
Assert.assertNotNull(userInfo.getSubject());
|
||||
Assert.assertEquals("test-user@localhost", userInfo.getEmail());
|
||||
Assert.assertEquals("test-user@localhost", userInfo.getPreferredUsername());
|
||||
|
||||
Assert.assertTrue(userInfo.hasAudience("test-app"));
|
||||
String expectedIssuer = Urls.realmIssuer(new URI(AUTH_SERVER_ROOT), "test");
|
||||
Assert.assertEquals(expectedIssuer, userInfo.getIssuer());
|
||||
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
|
||||
// Revert signed userInfo request
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setUserInfoSignedResponseAlg(null);
|
||||
clientResource.update(clientRep);
|
||||
} finally {
|
||||
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, org.keycloak.crypto.Algorithm.RS256);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import org.jboss.arquillian.graphene.page.Page;
|
|||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.crypto.Algorithm;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.jose.jws.JWSHeader;
|
||||
|
@ -248,36 +249,44 @@ public abstract class AbstractOIDCResponseTypeTest extends AbstractTestRealmKeyc
|
|||
}
|
||||
|
||||
@Test
|
||||
public void oidcFlow_RealmRS256_ClientRS384_EffectiveRS384() throws Exception {
|
||||
try {
|
||||
setSignatureAlgorithm("RS384");
|
||||
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, "RS256");
|
||||
TokenSignatureUtil.changeClientIdTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), "RS384");
|
||||
oidcFlow("RS256", "RS384");
|
||||
} finally {
|
||||
setSignatureAlgorithm("RS256");
|
||||
TokenSignatureUtil.changeClientIdTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), "RS256");
|
||||
}
|
||||
public void oidcFlow_RealmRS256_ClientRS384() throws Exception {
|
||||
oidcFlowRequest(Algorithm.RS256, Algorithm.RS384);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void oidcFlow_RealmES256_ClientES384_EffectiveES384() throws Exception {
|
||||
public void oidcFlow_RealmES256_ClientES384() throws Exception {
|
||||
oidcFlowRequest(Algorithm.ES256, Algorithm.ES384);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void oidcFlow_RealmRS256_ClientPS256() throws Exception {
|
||||
oidcFlowRequest(Algorithm.RS256, Algorithm.PS256);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void oidcFlow_RealmPS256_ClientES256() throws Exception {
|
||||
oidcFlowRequest(Algorithm.PS256, Algorithm.ES256);
|
||||
}
|
||||
|
||||
private void oidcFlowRequest(String expectedAccessAlg, String expectedIdTokenAlg) throws Exception {
|
||||
try {
|
||||
setSignatureAlgorithm("ES384");
|
||||
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, "ES256");
|
||||
TokenSignatureUtil.changeClientIdTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), "ES384");
|
||||
oidcFlow("ES256", "ES384");
|
||||
setIdTokenSignatureAlgorithm(expectedIdTokenAlg);
|
||||
// Realm setting is used for access token signature algorithm
|
||||
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, expectedAccessAlg);
|
||||
TokenSignatureUtil.changeClientIdTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), expectedIdTokenAlg);
|
||||
oidcFlow(expectedAccessAlg, expectedIdTokenAlg);
|
||||
} finally {
|
||||
setSignatureAlgorithm("RS256");
|
||||
TokenSignatureUtil.changeClientIdTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), "RS256");
|
||||
setIdTokenSignatureAlgorithm(Algorithm.RS256);
|
||||
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.RS256);
|
||||
TokenSignatureUtil.changeClientIdTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), Algorithm.RS256);
|
||||
}
|
||||
}
|
||||
|
||||
private String sigAlgName = "RS256";
|
||||
private void setSignatureAlgorithm(String sigAlgName) {
|
||||
this.sigAlgName = sigAlgName;
|
||||
private String idTokenSigAlgName = Algorithm.RS256;
|
||||
private void setIdTokenSignatureAlgorithm(String idTokenSigAlgName) {
|
||||
this.idTokenSigAlgName = idTokenSigAlgName;
|
||||
}
|
||||
protected String getSignatureAlgorithm() {
|
||||
return this.sigAlgName;
|
||||
protected String getIdTokenSignatureAlgorithm() {
|
||||
return this.idTokenSigAlgName;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,14 +64,14 @@ public class OIDCHybridResponseTypeCodeIDTokenTest extends AbstractOIDCResponseT
|
|||
Assert.assertNull(idToken.getAccessTokenHash());
|
||||
Assert.assertNotNull(idToken.getCodeHash());
|
||||
|
||||
Assert.assertEquals(idToken.getCodeHash(), HashUtils.oidcHash(getSignatureAlgorithm(), authzResponse.getCode()));
|
||||
Assert.assertEquals(idToken.getCodeHash(), HashUtils.oidcHash(getIdTokenSignatureAlgorithm(), authzResponse.getCode()));
|
||||
|
||||
// Financial API - Part 2: Read and Write API Security Profile
|
||||
// http://openid.net/specs/openid-financial-api-part-2.html#authorization-server
|
||||
// Validate "s_hash"
|
||||
Assert.assertNotNull(idToken.getStateHash());
|
||||
|
||||
Assert.assertEquals(idToken.getStateHash(), HashUtils.oidcHash(getSignatureAlgorithm(), authzResponse.getState()));
|
||||
Assert.assertEquals(idToken.getStateHash(), HashUtils.oidcHash(getIdTokenSignatureAlgorithm(), authzResponse.getState()));
|
||||
|
||||
// IDToken exchanged for the code
|
||||
IDToken idToken2 = sendTokenRequestAndGetIDToken(loginEvent);
|
||||
|
|
|
@ -63,17 +63,17 @@ public class OIDCHybridResponseTypeCodeIDTokenTokenTest extends AbstractOIDCResp
|
|||
// Validate "at_hash" and "c_hash"
|
||||
Assert.assertNotNull(idToken.getAccessTokenHash());
|
||||
|
||||
Assert.assertEquals(idToken.getAccessTokenHash(), HashUtils.oidcHash(getSignatureAlgorithm(), authzResponse.getAccessToken()));
|
||||
Assert.assertEquals(idToken.getAccessTokenHash(), HashUtils.oidcHash(getIdTokenSignatureAlgorithm(), authzResponse.getAccessToken()));
|
||||
Assert.assertNotNull(idToken.getCodeHash());
|
||||
|
||||
Assert.assertEquals(idToken.getCodeHash(), HashUtils.oidcHash(getSignatureAlgorithm(), authzResponse.getCode()));
|
||||
Assert.assertEquals(idToken.getCodeHash(), HashUtils.oidcHash(getIdTokenSignatureAlgorithm(), authzResponse.getCode()));
|
||||
|
||||
// Financial API - Part 2: Read and Write API Security Profile
|
||||
// http://openid.net/specs/openid-financial-api-part-2.html#authorization-server
|
||||
// Validate "s_hash"
|
||||
Assert.assertNotNull(idToken.getStateHash());
|
||||
|
||||
Assert.assertEquals(idToken.getStateHash(), HashUtils.oidcHash(getSignatureAlgorithm(), authzResponse.getState()));
|
||||
Assert.assertEquals(idToken.getStateHash(), HashUtils.oidcHash(getIdTokenSignatureAlgorithm(), authzResponse.getState()));
|
||||
|
||||
// IDToken exchanged for the code
|
||||
IDToken idToken2 = sendTokenRequestAndGetIDToken(loginEvent);
|
||||
|
|
|
@ -62,7 +62,7 @@ public class OIDCImplicitResponseTypeIDTokenTokenTest extends AbstractOIDCRespon
|
|||
// Validate "at_hash"
|
||||
Assert.assertNotNull(idToken.getAccessTokenHash());
|
||||
|
||||
Assert.assertEquals(idToken.getAccessTokenHash(), HashUtils.oidcHash(getSignatureAlgorithm(), authzResponse.getAccessToken()));
|
||||
Assert.assertEquals(idToken.getAccessTokenHash(), HashUtils.oidcHash(getIdTokenSignatureAlgorithm(), authzResponse.getAccessToken()));
|
||||
Assert.assertNull(idToken.getCodeHash());
|
||||
|
||||
return Collections.singletonList(idToken);
|
||||
|
|
Loading…
Reference in a new issue