KEYCLOAK-9756 PS256 algorithm support for token signing and validation

This commit is contained in:
Takashi Norimatsu 2018-12-17 06:29:43 +09:00 committed by Marek Posolda
parent b4973ad7b5
commit 9b3e297cd0
34 changed files with 757 additions and 364 deletions

View file

@ -27,6 +27,9 @@ public interface Algorithm {
String ES256 = "ES256"; String ES256 = "ES256";
String ES384 = "ES384"; String ES384 = "ES384";
String ES512 = "ES512"; String ES512 = "ES512";
String PS256 = "PS256";
String PS384 = "PS384";
String PS512 = "PS512";
String AES = "AES"; String AES = "AES";
} }

View file

@ -27,6 +27,9 @@ public class JavaAlgorithm {
public static final String ES256 = "SHA256withECDSA"; public static final String ES256 = "SHA256withECDSA";
public static final String ES384 = "SHA384withECDSA"; public static final String ES384 = "SHA384withECDSA";
public static final String ES512 = "SHA512withECDSA"; 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 AES = "AES";
public static final String SHA256 = "SHA-256"; public static final String SHA256 = "SHA-256";
@ -53,6 +56,12 @@ public class JavaAlgorithm {
return ES384; return ES384;
case Algorithm.ES512: case Algorithm.ES512:
return ES512; return ES512;
case Algorithm.PS256:
return PS256;
case Algorithm.PS384:
return PS384;
case Algorithm.PS512:
return PS512;
case Algorithm.AES: case Algorithm.AES:
return AES; return AES;
default: default:
@ -81,6 +90,12 @@ public class JavaAlgorithm {
return SHA384; return SHA384;
case Algorithm.ES512: case Algorithm.ES512:
return SHA512; return SHA512;
case Algorithm.PS256:
return SHA256;
case Algorithm.PS384:
return SHA384;
case Algorithm.PS512:
return SHA512;
case Algorithm.AES: case Algorithm.AES:
return AES; return AES;
default: default:

View file

@ -34,6 +34,9 @@ public enum Algorithm {
RS256(AlgorithmType.RSA, new RSAProvider()), RS256(AlgorithmType.RSA, new RSAProvider()),
RS384(AlgorithmType.RSA, new RSAProvider()), RS384(AlgorithmType.RSA, new RSAProvider()),
RS512(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), ES256(AlgorithmType.ECDSA, null),
ES384(AlgorithmType.ECDSA, null), ES384(AlgorithmType.ECDSA, null),
ES512(AlgorithmType.ECDSA, null) ES512(AlgorithmType.ECDSA, null)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -55,9 +55,10 @@ public interface Attributes {
"16", "24", "32", "64", "128", "256", "512"); "16", "24", "32", "64", "128", "256", "512");
String ALGORITHM_KEY = "algorithm"; String ALGORITHM_KEY = "algorithm";
ProviderConfigProperty RS_ALGORITHM_PROPERTY = new ProviderConfigProperty(ALGORITHM_KEY, "Algorithm", "Intended algorithm for the key", LIST_TYPE, ProviderConfigProperty RS_ALGORITHM_PROPERTY = new ProviderConfigProperty(ALGORITHM_KEY, "Algorithm", "Intended algorithm for the key", LIST_TYPE,
Algorithm.RS256, 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, ProviderConfigProperty HS_ALGORITHM_PROPERTY = new ProviderConfigProperty(ALGORITHM_KEY, "Algorithm", "Intended algorithm for the key", LIST_TYPE,
Algorithm.HS256, Algorithm.HS256,

View file

@ -59,7 +59,7 @@ public class GeneratedRsaKeyProviderFactory extends AbstractRsaKeyProviderFactor
@Override @Override
public boolean createFallbackKeys(KeycloakSession session, KeyUse keyUse, String algorithm) { 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(); RealmModel realm = session.getContext().getRealm();
ComponentModel generated = new ComponentModel(); 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 @Override
public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel model) throws ComponentValidationException { public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel model) throws ComponentValidationException {
super.validateConfiguration(session, realm, model); super.validateConfiguration(session, realm, model);

View file

@ -3,4 +3,7 @@ org.keycloak.crypto.RS384ClientSignatureVerifierProviderFactory
org.keycloak.crypto.RS512ClientSignatureVerifierProviderFactory org.keycloak.crypto.RS512ClientSignatureVerifierProviderFactory
org.keycloak.crypto.ES256ClientSignatureVerifierProviderFactory org.keycloak.crypto.ES256ClientSignatureVerifierProviderFactory
org.keycloak.crypto.ES384ClientSignatureVerifierProviderFactory org.keycloak.crypto.ES384ClientSignatureVerifierProviderFactory
org.keycloak.crypto.ES512ClientSignatureVerifierProviderFactory org.keycloak.crypto.ES512ClientSignatureVerifierProviderFactory
org.keycloak.crypto.PS256ClientSignatureVerifierProviderFactory
org.keycloak.crypto.PS384ClientSignatureVerifierProviderFactory
org.keycloak.crypto.PS512ClientSignatureVerifierProviderFactory

View file

@ -6,4 +6,7 @@ org.keycloak.crypto.HS384SignatureProviderFactory
org.keycloak.crypto.HS512SignatureProviderFactory org.keycloak.crypto.HS512SignatureProviderFactory
org.keycloak.crypto.ES256SignatureProviderFactory org.keycloak.crypto.ES256SignatureProviderFactory
org.keycloak.crypto.ES384SignatureProviderFactory org.keycloak.crypto.ES384SignatureProviderFactory
org.keycloak.crypto.ES512SignatureProviderFactory org.keycloak.crypto.ES512SignatureProviderFactory
org.keycloak.crypto.PS256SignatureProviderFactory
org.keycloak.crypto.PS384SignatureProviderFactory
org.keycloak.crypto.PS512SignatureProviderFactory

View file

@ -22,6 +22,7 @@ import org.jboss.resteasy.spi.BadRequestException;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.common.util.KeyUtils; import org.keycloak.common.util.KeyUtils;
import org.keycloak.common.util.PemUtils; import org.keycloak.common.util.PemUtils;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.AsymmetricSignatureSignerContext; import org.keycloak.crypto.AsymmetricSignatureSignerContext;
import org.keycloak.crypto.KeyType; import org.keycloak.crypto.KeyType;
import org.keycloak.crypto.KeyWrapper; 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.JSONWebKeySet;
import org.keycloak.jose.jwk.JWK; import org.keycloak.jose.jwk.JWK;
import org.keycloak.jose.jwk.JWKBuilder; import org.keycloak.jose.jwk.JWKBuilder;
import org.keycloak.jose.jws.Algorithm;
import org.keycloak.jose.jws.JWSBuilder; import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.testsuite.rest.TestApplicationResourceProviderFactory; import org.keycloak.testsuite.rest.TestApplicationResourceProviderFactory;
@ -77,21 +77,24 @@ public class TestingOIDCEndpointsApplicationResource {
String keyType = null; String keyType = null;
switch (jwaAlgorithm) { switch (jwaAlgorithm) {
case org.keycloak.crypto.Algorithm.RS256: case Algorithm.RS256:
case org.keycloak.crypto.Algorithm.RS384: case Algorithm.RS384:
case org.keycloak.crypto.Algorithm.RS512: case Algorithm.RS512:
case Algorithm.PS256:
case Algorithm.PS384:
case Algorithm.PS512:
keyType = KeyType.RSA; keyType = KeyType.RSA;
keyPair = KeyUtils.generateRsaKeyPair(2048); keyPair = KeyUtils.generateRsaKeyPair(2048);
break; break;
case org.keycloak.crypto.Algorithm.ES256: case Algorithm.ES256:
keyType = KeyType.EC; keyType = KeyType.EC;
keyPair = generateEcdsaKey("secp256r1"); keyPair = generateEcdsaKey("secp256r1");
break; break;
case org.keycloak.crypto.Algorithm.ES384: case Algorithm.ES384:
keyType = KeyType.EC; keyType = KeyType.EC;
keyPair = generateEcdsaKey("secp384r1"); keyPair = generateEcdsaKey("secp384r1");
break; break;
case org.keycloak.crypto.Algorithm.ES512: case Algorithm.ES512:
keyType = KeyType.EC; keyType = KeyType.EC;
keyPair = generateEcdsaKey("secp521r1"); keyPair = generateEcdsaKey("secp521r1");
break; break;
@ -193,12 +196,15 @@ public class TestingOIDCEndpointsApplicationResource {
boolean ret = false; boolean ret = false;
switch (signingAlgorithm) { switch (signingAlgorithm) {
case "none": case "none":
case org.keycloak.crypto.Algorithm.RS256: case Algorithm.RS256:
case org.keycloak.crypto.Algorithm.RS384: case Algorithm.RS384:
case org.keycloak.crypto.Algorithm.RS512: case Algorithm.RS512:
case org.keycloak.crypto.Algorithm.ES256: case Algorithm.PS256:
case org.keycloak.crypto.Algorithm.ES384: case Algorithm.PS384:
case org.keycloak.crypto.Algorithm.ES512: case Algorithm.PS512:
case Algorithm.ES256:
case Algorithm.ES384:
case Algorithm.ES512:
ret = true; ret = true;
} }
return ret; return ret;

View file

@ -1205,7 +1205,7 @@ public class OAuthClient {
PublicKey publicKey = JWKParser.create(k).toPublicKey(); PublicKey publicKey = JWKParser.create(k).toPublicKey();
KeyWrapper key = new KeyWrapper(); KeyWrapper key = new KeyWrapper();
key.setKid(key.getKid()); key.setKid(k.getKeyId());
key.setAlgorithm(k.getAlgorithm()); key.setAlgorithm(k.getAlgorithm());
key.setVerifyKey(publicKey); key.setVerifyKey(publicKey);
key.setUse(KeyUse.SIG); key.setUse(KeyUse.SIG);

View file

@ -23,7 +23,6 @@ import java.security.PublicKey;
import java.security.Signature; import java.security.Signature;
import java.security.spec.InvalidKeySpecException; import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec; import java.security.spec.X509EncodedKeySpec;
import java.util.Map;
import javax.ws.rs.core.Response; 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.admin.client.resource.ClientResource;
import org.keycloak.common.util.Base64; import org.keycloak.common.util.Base64;
import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.JavaAlgorithm; import org.keycloak.crypto.JavaAlgorithm;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.keys.GeneratedEcdsaKeyProviderFactory; import org.keycloak.keys.GeneratedEcdsaKeyProviderFactory;
import org.keycloak.keys.GeneratedRsaKeyProviderFactory;
import org.keycloak.keys.KeyProvider; import org.keycloak.keys.KeyProvider;
import org.keycloak.protocol.oidc.OIDCConfigAttributes; import org.keycloak.protocol.oidc.OIDCConfigAttributes;
import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientRepresentation;
@ -47,9 +48,6 @@ import org.keycloak.testsuite.arquillian.TestContext;
public class TokenSignatureUtil { public class TokenSignatureUtil {
private static Logger log = Logger.getLogger(TokenSignatureUtil.class); 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"; private static final String TEST_REALM_NAME = "test";
public static void changeRealmTokenSignatureProvider(Keycloak adminClient, String toSigAlgName) { 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) { public static void changeRealmTokenSignatureProvider(String realm, Keycloak adminClient, String toSigAlgName) {
RealmRepresentation rep = adminClient.realm(realm).toRepresentation(); RealmRepresentation rep = adminClient.realm(realm).toRepresentation();
Map<String, String> attributes = rep.getAttributes(); log.tracef("change realm test signature algorithm from %s to %s", rep.getDefaultSignatureAlgorithm(), toSigAlgName);
log.tracef("change realm test signature algorithm from %s to %s", attributes.get(COMPONENT_SIGNATURE_ALGORITHM_KEY), toSigAlgName);
rep.setDefaultSignatureAlgorithm(toSigAlgName); rep.setDefaultSignatureAlgorithm(toSigAlgName);
rep.setAttributes(attributes);
adminClient.realm(realm).update(rep); adminClient.realm(realm).update(rep);
} }
@ -88,17 +84,48 @@ public class TokenSignatureUtil {
return verifier.verify(jws.getSignature()); return verifier.verify(jws.getSignature());
} }
public static void registerKeyProvider(String ecNistRep, Keycloak adminClient, TestContext testContext) { public static void registerKeyProvider(String jwaAlgorithmName, Keycloak adminClient, TestContext testContext) {
registerKeyProvider(TEST_REALM_NAME, ecNistRep, adminClient, 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(); long priority = System.currentTimeMillis();
ComponentRepresentation rep = createKeyRep("valid", GeneratedEcdsaKeyProviderFactory.ID); ComponentRepresentation rep = createKeyRep("valid", providerId);
rep.setConfig(new MultivaluedHashMap<>()); rep.setConfig(new MultivaluedHashMap<>());
rep.getConfig().putSingle("priority", Long.toString(priority)); 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)) { try (Response response = adminClient.realm(realm).components().add(rep)) {
String id = ApiUtil.getCreatedId(response); String id = ApiUtil.getCreatedId(response);

View file

@ -47,7 +47,6 @@ public class AdminSignatureAlgorithmTest extends AbstractKeycloakTest {
@Test @Test
public void changeRealmTokenAlgorithm() throws Exception { public void changeRealmTokenAlgorithm() throws Exception {
TokenSignatureUtil.registerKeyProvider("master", "P-256", adminClient, testContext);
TokenSignatureUtil.changeRealmTokenSignatureProvider("master", adminClient, Algorithm.ES256); TokenSignatureUtil.changeRealmTokenSignatureProvider("master", adminClient, Algorithm.ES256);
try (Keycloak adminClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(), suiteContext.getAuthServerInfo().getContextRoot().toString())) { try (Keycloak adminClient = AdminClientUtil.createAdminClient(suiteContext.isAdapterCompatTesting(), suiteContext.getAuthServerInfo().getContextRoot().toString())) {
@ -56,7 +55,7 @@ public class AdminSignatureAlgorithmTest extends AbstractKeycloakTest {
assertEquals(Algorithm.ES256, verifier.getHeader().getAlgorithm().name()); assertEquals(Algorithm.ES256, verifier.getHeader().getAlgorithm().name());
assertNotNull(adminClient.realms().findAll()); assertNotNull(adminClient.realms().findAll());
String whoAmiUrl = suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth/admin/master/console/whoami"; String whoAmiUrl = suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth/admin/master/console/whoami";
JsonNode jsonNode = SimpleHttp.doGet(whoAmiUrl, client).auth(accessToken.getToken()).asJson(); JsonNode jsonNode = SimpleHttp.doGet(whoAmiUrl, client).auth(accessToken.getToken()).asJson();

View file

@ -73,7 +73,6 @@ public class InitialAccessTokenTest extends AbstractClientRegistrationTest {
@Test @Test
public void createWithES256() throws JWSInputException, ClientRegistrationException { public void createWithES256() throws JWSInputException, ClientRegistrationException {
try { try {
TokenSignatureUtil.registerKeyProvider("P-256", adminClient, testContext);
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.ES256); TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.ES256);
ClientInitialAccessPresentation response = resource.create(new ClientInitialAccessCreatePresentation()); ClientInitialAccessPresentation response = resource.create(new ClientInitialAccessCreatePresentation());
@ -82,7 +81,7 @@ public class InitialAccessTokenTest extends AbstractClientRegistrationTest {
String token = response.getToken(); String token = response.getToken();
JWSHeader header = new JWSInput(token).getHeader(); JWSHeader header = new JWSInput(token).getHeader();
assertEquals("HS256", header.getAlgorithm().name()); assertEquals(Algorithm.HS256, header.getAlgorithm().name());
ClientRepresentation rep = new ClientRepresentation(); ClientRepresentation rep = new ClientRepresentation();
ClientRepresentation created = reg.create(rep); ClientRepresentation created = reg.create(rep);

View file

@ -206,33 +206,42 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest {
@Test @Test
public void testSignaturesRequired() throws Exception { public void testSignaturesRequired() throws Exception {
OIDCClientRepresentation clientRep = createRep(); OIDCClientRepresentation clientRep = null;
clientRep.setUserinfoSignedResponseAlg(Algorithm.ES256.toString()); OIDCClientRepresentation response = null;
clientRep.setRequestObjectSigningAlg(Algorithm.ES256.toString()); try {
clientRep = createRep();
clientRep.setUserinfoSignedResponseAlg(Algorithm.ES256.toString());
clientRep.setRequestObjectSigningAlg(Algorithm.ES256.toString());
OIDCClientRepresentation response = reg.oidc().create(clientRep); response = reg.oidc().create(clientRep);
Assert.assertEquals(Algorithm.ES256.toString(), response.getUserinfoSignedResponseAlg()); Assert.assertEquals(Algorithm.ES256.toString(), response.getUserinfoSignedResponseAlg());
Assert.assertEquals(Algorithm.ES256.toString(), response.getRequestObjectSigningAlg()); Assert.assertEquals(Algorithm.ES256.toString(), response.getRequestObjectSigningAlg());
Assert.assertNotNull(response.getClientSecret()); Assert.assertNotNull(response.getClientSecret());
// Test Keycloak representation // Test Keycloak representation
ClientRepresentation kcClient = getClient(response.getClientId()); ClientRepresentation kcClient = getClient(response.getClientId());
OIDCAdvancedConfigWrapper config = OIDCAdvancedConfigWrapper.fromClientRepresentation(kcClient); OIDCAdvancedConfigWrapper config = OIDCAdvancedConfigWrapper.fromClientRepresentation(kcClient);
Assert.assertEquals(config.getUserInfoSignedResponseAlg(), Algorithm.ES256); Assert.assertEquals(config.getUserInfoSignedResponseAlg(), Algorithm.ES256);
Assert.assertEquals(config.getRequestObjectSignatureAlg(), Algorithm.ES256); Assert.assertEquals(config.getRequestObjectSignatureAlg(), Algorithm.ES256);
// update (ES256 to RS256) // update (ES256 to PS256)
clientRep.setUserinfoSignedResponseAlg(Algorithm.RS256.toString()); clientRep.setUserinfoSignedResponseAlg(Algorithm.PS256.toString());
clientRep.setRequestObjectSigningAlg(Algorithm.RS256.toString()); clientRep.setRequestObjectSigningAlg(Algorithm.PS256.toString());
response = reg.oidc().create(clientRep); response = reg.oidc().create(clientRep);
Assert.assertEquals(Algorithm.RS256.toString(), response.getUserinfoSignedResponseAlg()); Assert.assertEquals(Algorithm.PS256.toString(), response.getUserinfoSignedResponseAlg());
Assert.assertEquals(Algorithm.RS256.toString(), response.getRequestObjectSigningAlg()); Assert.assertEquals(Algorithm.PS256.toString(), response.getRequestObjectSigningAlg());
// keycloak representation // keycloak representation
kcClient = getClient(response.getClientId()); kcClient = getClient(response.getClientId());
config = OIDCAdvancedConfigWrapper.fromClientRepresentation(kcClient); config = OIDCAdvancedConfigWrapper.fromClientRepresentation(kcClient);
Assert.assertEquals(config.getUserInfoSignedResponseAlg(), Algorithm.RS256); Assert.assertEquals(config.getUserInfoSignedResponseAlg(), Algorithm.PS256);
Assert.assertEquals(config.getRequestObjectSignatureAlg(), Algorithm.RS256); 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 @Test

View file

@ -379,7 +379,6 @@ public class LoginTest extends AbstractTestRealmKeycloakTest {
assertEquals("HS256", algorithm); assertEquals("HS256", algorithm);
try { try {
TokenSignatureUtil.registerKeyProvider("P-256", adminClient, testContext);
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.ES256); TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.ES256);
oauth.openLoginForm(); oauth.openLoginForm();

View file

@ -96,6 +96,9 @@ public class FallbackKeyProviderTest extends AbstractKeycloakTest {
String[] algorithmsToTest = new String[] { String[] algorithmsToTest = new String[] {
Algorithm.RS384, Algorithm.RS384,
Algorithm.RS512, Algorithm.RS512,
Algorithm.PS256,
Algorithm.PS384,
Algorithm.PS512,
Algorithm.ES256, Algorithm.ES256,
Algorithm.ES384, Algorithm.ES384,
Algorithm.ES512 Algorithm.ES512

View file

@ -1057,66 +1057,6 @@ public class AccessTokenTest extends AbstractKeycloakTest {
.post(Entity.form(form)); .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 @Test
public void clientAccessTokenLifespanOverride() { public void clientAccessTokenLifespanOverride() {
ClientResource client = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"); 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 { private void tokenRequest(String expectedRefreshAlg, String expectedAccessAlg, String expectedIdTokenAlg) throws Exception {
oauth.doLogin("test-user@localhost", "password"); oauth.doLogin("test-user@localhost", "password");
@ -1203,34 +1196,5 @@ public class AccessTokenTest extends AbstractKeycloakTest {
assertEquals(oauth.parseRefreshToken(response.getRefreshToken()).getId(), event.getDetails().get(Details.REFRESH_TOKEN_ID)); assertEquals(oauth.parseRefreshToken(response.getRefreshToken()).getId(), event.getDetails().get(Details.REFRESH_TOKEN_ID));
assertEquals(sessionId, token.getSessionState()); 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);
}
} }

View file

@ -30,6 +30,7 @@ import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.RoleResource; import org.keycloak.admin.client.resource.RoleResource;
import org.keycloak.admin.client.resource.UserResource; import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.common.constants.ServiceAccountConstants; import org.keycloak.common.constants.ServiceAccountConstants;
import org.keycloak.crypto.Algorithm;
import org.keycloak.events.Details; import org.keycloak.events.Details;
import org.keycloak.events.Errors; import org.keycloak.events.Errors;
import org.keycloak.jose.jws.JWSHeader; 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); 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) { private void testOfflineSessionExpiration(int idleTime, int maxLifespan, int offset) {
int prev[] = null; int prev[] = null;
try { 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");
}
}
} }

View file

@ -871,6 +871,46 @@ public class RefreshTokenTest extends AbstractKeycloakTest {
assertNotNull(response.getRefreshToken()); 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) { protected Response executeRefreshToken(WebTarget refreshTarget, String refreshToken) {
String header = BasicAuthHelper.createHeader("test-app", "password"); String header = BasicAuthHelper.createHeader("test-app", "password");
Form form = new Form(); Form form = new Form();
@ -892,6 +932,18 @@ public class RefreshTokenTest extends AbstractKeycloakTest {
.post(Entity.form(form)); .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 { private void refreshToken(String expectedRefreshAlg, String expectedAccessAlg, String expectedIdTokenAlg) throws Exception {
oauth.doLogin("test-user@localhost", "password"); oauth.doLogin("test-user@localhost", "password");
@ -957,118 +1009,4 @@ public class RefreshTokenTest extends AbstractKeycloakTest {
setTimeOffset(0); 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);
}
} }

View file

@ -26,8 +26,11 @@ import org.junit.Test;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator; import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
import org.keycloak.crypto.Algorithm;
import org.keycloak.events.Details; import org.keycloak.events.Details;
import org.keycloak.events.Errors; 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.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.TimeBasedOTP; 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.AbstractKeycloakTest;
import org.keycloak.testsuite.Assert; import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.util.ClientBuilder; import org.keycloak.testsuite.util.ClientBuilder;
import org.keycloak.testsuite.util.ClientManager; import org.keycloak.testsuite.util.ClientManager;
import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.testsuite.util.RealmBuilder; import org.keycloak.testsuite.util.RealmBuilder;
import org.keycloak.testsuite.util.RealmManager; import org.keycloak.testsuite.util.RealmManager;
import org.keycloak.testsuite.util.TokenSignatureUtil;
import org.keycloak.testsuite.util.UserBuilder; import org.keycloak.testsuite.util.UserBuilder;
import org.keycloak.testsuite.util.UserManager; import org.keycloak.testsuite.util.UserManager;
import java.util.List; import java.util.List;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @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(); 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 @Test
public void grantAccessTokenLogout() throws Exception { public void grantAccessTokenLogout() throws Exception {
oauth.clientId("resource-owner"); oauth.clientId("resource-owner");

View file

@ -23,8 +23,11 @@ import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator; import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
import org.keycloak.common.constants.ServiceAccountConstants; import org.keycloak.common.constants.ServiceAccountConstants;
import org.keycloak.crypto.Algorithm;
import org.keycloak.events.Details; import org.keycloak.events.Details;
import org.keycloak.events.Errors; 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.models.utils.KeycloakModelUtils;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.representations.RefreshToken; import org.keycloak.representations.RefreshToken;
@ -33,15 +36,18 @@ import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest; import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.util.ClientBuilder; import org.keycloak.testsuite.util.ClientBuilder;
import org.keycloak.testsuite.util.ClientManager; import org.keycloak.testsuite.util.ClientManager;
import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.testsuite.util.RealmBuilder; import org.keycloak.testsuite.util.RealmBuilder;
import org.keycloak.testsuite.util.TokenSignatureUtil;
import org.keycloak.testsuite.util.UserBuilder; import org.keycloak.testsuite.util.UserBuilder;
import java.util.List; import java.util.List;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
/** /**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -269,4 +275,68 @@ public class ServiceAccountTest extends AbstractKeycloakTest {
userName = user.getUsername(); 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();
}
} }

View file

@ -18,7 +18,6 @@ package org.keycloak.testsuite.oauth;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.TextNode; import com.fasterxml.jackson.databind.node.TextNode;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
@ -249,16 +248,24 @@ public class TokenIntrospectionTest extends AbstractTestRealmKeycloakTest {
@Test @Test
public void testIntrospectAccessTokenES256() throws Exception { 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 { try {
TokenSignatureUtil.registerKeyProvider("P-256", adminClient, testContext); TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), jwaAlgorithm);
TokenSignatureUtil.changeClientAccessTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), Algorithm.ES256);
oauth.doLogin("test-user@localhost", "password"); oauth.doLogin("test-user@localhost", "password");
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
EventRepresentation loginEvent = events.expectLogin().assertEvent(); EventRepresentation loginEvent = events.expectLogin().assertEvent();
AccessTokenResponse accessTokenResponse = oauth.doAccessTokenRequest(code, "password"); 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()); String tokenResponse = oauth.introspectAccessTokenWithClientCredential("confidential-cli", "secret1", accessTokenResponse.getAccessToken());

View file

@ -930,6 +930,24 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
requestUriParamSignedIn(Algorithm.RS512, Algorithm.RS512); 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 @Test
public void requestUriParamSignedExpectedAnyActualES256() throws Exception { public void requestUriParamSignedExpectedAnyActualES256() throws Exception {
// Algorithm is null if 'any' // Algorithm is null if 'any'

View file

@ -124,9 +124,11 @@ public class OIDCWellKnownProviderTest extends AbstractKeycloakTest {
assertContains(oidcConfig.getResponseModesSupported(), "query", "fragment"); assertContains(oidcConfig.getResponseModesSupported(), "query", "fragment");
Assert.assertNames(oidcConfig.getSubjectTypesSupported(), "pairwise", "public"); 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); // Signature algorithms
Assert.assertNames(oidcConfig.getRequestObjectSigningAlgValuesSupported(), "none", Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512); 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 // Client authentication
Assert.assertNames(oidcConfig.getTokenEndpointAuthMethodsSupported(), "client_secret_basic", "client_secret_post", "private_key_jwt", "client_secret_jwt"); 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 @Test
public void certs() throws IOException { 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); OIDCConfigurationRepresentation representation = SimpleHttp.doGet(getAuthServerRoot().toString() + "realms/test/.well-known/openid-configuration", client).asJson(OIDCConfigurationRepresentation.class);
String jwksUri = representation.getJwksUri(); String jwksUri = representation.getJwksUri();

View file

@ -280,64 +280,14 @@ public class UserInfoTest extends AbstractKeycloakTest {
@Test @Test
public void testSuccessSignedResponseES256() throws Exception { 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 @Test
public void testSessionExpired() { public void testSessionExpired() {
Client client = ClientBuilder.newClient(); Client client = ClientBuilder.newClient();
@ -510,4 +460,62 @@ public class UserInfoTest extends AbstractKeycloakTest {
.assertEvent(); .assertEvent();
UserInfoClientUtil.testSuccessfulUserInfoResponse(response, "test-user@localhost", "test-user@localhost"); 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);
}
}
} }

View file

@ -21,6 +21,7 @@ import org.jboss.arquillian.graphene.page.Page;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.keycloak.OAuthErrorException; import org.keycloak.OAuthErrorException;
import org.keycloak.crypto.Algorithm;
import org.keycloak.events.Details; import org.keycloak.events.Details;
import org.keycloak.events.Errors; import org.keycloak.events.Errors;
import org.keycloak.jose.jws.JWSHeader; import org.keycloak.jose.jws.JWSHeader;
@ -248,36 +249,44 @@ public abstract class AbstractOIDCResponseTypeTest extends AbstractTestRealmKeyc
} }
@Test @Test
public void oidcFlow_RealmRS256_ClientRS384_EffectiveRS384() throws Exception { public void oidcFlow_RealmRS256_ClientRS384() throws Exception {
try { oidcFlowRequest(Algorithm.RS256, Algorithm.RS384);
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");
}
} }
@Test @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 { try {
setSignatureAlgorithm("ES384"); setIdTokenSignatureAlgorithm(expectedIdTokenAlg);
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, "ES256"); // Realm setting is used for access token signature algorithm
TokenSignatureUtil.changeClientIdTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), "ES384"); TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, expectedAccessAlg);
oidcFlow("ES256", "ES384"); TokenSignatureUtil.changeClientIdTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), expectedIdTokenAlg);
oidcFlow(expectedAccessAlg, expectedIdTokenAlg);
} finally { } finally {
setSignatureAlgorithm("RS256"); setIdTokenSignatureAlgorithm(Algorithm.RS256);
TokenSignatureUtil.changeClientIdTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), "RS256"); TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, Algorithm.RS256);
TokenSignatureUtil.changeClientIdTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), Algorithm.RS256);
} }
} }
private String sigAlgName = "RS256"; private String idTokenSigAlgName = Algorithm.RS256;
private void setSignatureAlgorithm(String sigAlgName) { private void setIdTokenSignatureAlgorithm(String idTokenSigAlgName) {
this.sigAlgName = sigAlgName; this.idTokenSigAlgName = idTokenSigAlgName;
} }
protected String getSignatureAlgorithm() { protected String getIdTokenSignatureAlgorithm() {
return this.sigAlgName; return this.idTokenSigAlgName;
} }
} }

View file

@ -64,14 +64,14 @@ public class OIDCHybridResponseTypeCodeIDTokenTest extends AbstractOIDCResponseT
Assert.assertNull(idToken.getAccessTokenHash()); Assert.assertNull(idToken.getAccessTokenHash());
Assert.assertNotNull(idToken.getCodeHash()); 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 // Financial API - Part 2: Read and Write API Security Profile
// http://openid.net/specs/openid-financial-api-part-2.html#authorization-server // http://openid.net/specs/openid-financial-api-part-2.html#authorization-server
// Validate "s_hash" // Validate "s_hash"
Assert.assertNotNull(idToken.getStateHash()); 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 exchanged for the code
IDToken idToken2 = sendTokenRequestAndGetIDToken(loginEvent); IDToken idToken2 = sendTokenRequestAndGetIDToken(loginEvent);

View file

@ -63,17 +63,17 @@ public class OIDCHybridResponseTypeCodeIDTokenTokenTest extends AbstractOIDCResp
// Validate "at_hash" and "c_hash" // Validate "at_hash" and "c_hash"
Assert.assertNotNull(idToken.getAccessTokenHash()); 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.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 // Financial API - Part 2: Read and Write API Security Profile
// http://openid.net/specs/openid-financial-api-part-2.html#authorization-server // http://openid.net/specs/openid-financial-api-part-2.html#authorization-server
// Validate "s_hash" // Validate "s_hash"
Assert.assertNotNull(idToken.getStateHash()); 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 exchanged for the code
IDToken idToken2 = sendTokenRequestAndGetIDToken(loginEvent); IDToken idToken2 = sendTokenRequestAndGetIDToken(loginEvent);

View file

@ -62,7 +62,7 @@ public class OIDCImplicitResponseTypeIDTokenTokenTest extends AbstractOIDCRespon
// Validate "at_hash" // Validate "at_hash"
Assert.assertNotNull(idToken.getAccessTokenHash()); 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()); Assert.assertNull(idToken.getCodeHash());
return Collections.singletonList(idToken); return Collections.singletonList(idToken);