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 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";
|
||||||
}
|
}
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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");
|
"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,
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in a new issue