KEYCLOAK-9089 IllegalArgumentException when trying to use ES256 as OIDC access token signature

This commit is contained in:
mposolda 2018-12-14 11:06:21 +01:00 committed by Marek Posolda
parent 4150daa9cb
commit 061693a8c9
22 changed files with 472 additions and 61 deletions

View file

@ -37,6 +37,11 @@ public class AsymmetricSignatureSignerContext implements SignatureSignerContext
return key.getAlgorithm(); return key.getAlgorithm();
} }
@Override
public String getHashAlgorithm() {
return JavaAlgorithm.getJavaAlgorithmForHash(key.getAlgorithm());
}
@Override @Override
public byte[] sign(byte[] data) throws SignatureException { public byte[] sign(byte[] data) throws SignatureException {
try { try {

View file

@ -0,0 +1,32 @@
/*
* Copyright 2017 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;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class HashException extends RuntimeException {
public HashException(String message) {
super(message);
}
public HashException(String message, Throwable cause) {
super(message, cause);
}
}

View file

@ -29,6 +29,10 @@ public class JavaAlgorithm {
public static final String ES512 = "SHA512withECDSA"; public static final String ES512 = "SHA512withECDSA";
public static final String AES = "AES"; public static final String AES = "AES";
public static final String SHA256 = "SHA-256";
public static final String SHA384 = "SHA-384";
public static final String SHA512 = "SHA-512";
public static String getJavaAlgorithm(String algorithm) { public static String getJavaAlgorithm(String algorithm) {
switch (algorithm) { switch (algorithm) {
case Algorithm.RS256: case Algorithm.RS256:
@ -56,4 +60,32 @@ public class JavaAlgorithm {
} }
} }
public static String getJavaAlgorithmForHash(String algorithm) {
switch (algorithm) {
case Algorithm.RS256:
return SHA256;
case Algorithm.RS384:
return SHA384;
case Algorithm.RS512:
return SHA512;
case Algorithm.HS256:
return SHA256;
case Algorithm.HS384:
return SHA384;
case Algorithm.HS512:
return SHA512;
case Algorithm.ES256:
return SHA256;
case Algorithm.ES384:
return SHA384;
case Algorithm.ES512:
return SHA512;
case Algorithm.AES:
return AES;
default:
throw new IllegalArgumentException("Unknown algorithm " + algorithm);
}
}
} }

View file

@ -36,6 +36,11 @@ public class MacSignatureSignerContext implements SignatureSignerContext {
return key.getAlgorithm(); return key.getAlgorithm();
} }
@Override
public String getHashAlgorithm() {
return JavaAlgorithm.getJavaAlgorithmForHash(key.getAlgorithm());
}
@Override @Override
public byte[] sign(byte[] data) throws SignatureException { public byte[] sign(byte[] data) throws SignatureException {
try { try {

View file

@ -22,6 +22,8 @@ public interface SignatureSignerContext {
String getAlgorithm(); String getAlgorithm();
String getHashAlgorithm();
byte[] sign(byte[] data) throws SignatureException; byte[] sign(byte[] data) throws SignatureException;
} }

View file

@ -18,51 +18,48 @@
package org.keycloak.jose.jws.crypto; package org.keycloak.jose.jws.crypto;
import org.keycloak.common.util.Base64Url; import org.keycloak.common.util.Base64Url;
import org.keycloak.jose.jws.Algorithm; import org.keycloak.crypto.HashException;
import org.keycloak.crypto.JavaAlgorithm;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.util.Arrays; import java.util.Arrays;
/** /**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/ */
public class HashProvider { public class HashUtils {
// See "at_hash" and "c_hash" in OIDC specification
public static String oidcHash(String jwtAlgorithmName, String input) { public static String oidcHash(String jwtAlgorithmName, String input) {
byte[] digest = digest(jwtAlgorithmName, input); try {
byte[] inputBytes = input.getBytes("UTF-8");
String javaAlgName = JavaAlgorithm.getJavaAlgorithmForHash(jwtAlgorithmName);
byte[] hash = hash(javaAlgName, inputBytes);
int hashLength = digest.length / 2; return encodeHashToOIDC(hash);
byte[] hashInput = Arrays.copyOf(digest, hashLength); } catch (UnsupportedEncodingException e) {
throw new HashException("Error when creating token hash", e);
}
}
public static byte[] hash(String javaAlgorithmName, byte[] inputBytes) {
try {
MessageDigest md = MessageDigest.getInstance(javaAlgorithmName);
md.update(inputBytes);
return md.digest();
} catch (Exception e) {
throw new HashException("Error when creating token hash", e);
}
}
public static String encodeHashToOIDC(byte[] hash) {
int hashLength = hash.length / 2;
byte[] hashInput = Arrays.copyOf(hash, hashLength);
return Base64Url.encode(hashInput); return Base64Url.encode(hashInput);
} }
private static byte[] digest(String algorithm, String input) {
String digestAlg = getJavaDigestAlgorithm(algorithm);
try {
MessageDigest md = MessageDigest.getInstance(digestAlg);
md.update(input.getBytes("UTF-8"));
return md.digest();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static String getJavaDigestAlgorithm(String alg) {
switch (alg) {
case "RS256":
return "SHA-256";
case "RS384":
return "SHA-384";
case "RS512":
return "SHA-512";
default:
throw new IllegalArgumentException("Not an RSA Algorithm");
}
}
// See "at_hash" and "c_hash" in OIDC specification
public static String oidcHash(Algorithm jwtAlgorithm, String input) {
return oidcHash(jwtAlgorithm.name(), input);
}
} }

View file

@ -20,8 +20,8 @@ package org.keycloak;
import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.keycloak.jose.jws.Algorithm; import org.keycloak.crypto.Algorithm;
import org.keycloak.jose.jws.crypto.HashProvider; import org.keycloak.jose.jws.crypto.HashUtils;
import java.security.Security; import java.security.Security;
@ -37,13 +37,19 @@ public class AtHashTest {
} }
@Test @Test
public void testAtHash() throws Exception { public void testAtHashRsa() throws Exception {
verifyHash("jHkWEdUXMU1BwAsC4vtUsZwnNvTIxEl0z9K3vx5KF0Y", "77QmUPtjPfzWtF2AnpK9RQ"); verifyHash(Algorithm.RS256,"jHkWEdUXMU1BwAsC4vtUsZwnNvTIxEl0z9K3vx5KF0Y", "77QmUPtjPfzWtF2AnpK9RQ");
verifyHash("ya29.eQETFbFOkAs8nWHcmYXKwEi0Zz46NfsrUU_KuQLOLTwWS40y6Fb99aVzEXC0U14m61lcPMIr1hEIBA", "aUAkJG-u6x4RTWuILWy-CA"); verifyHash(Algorithm.RS256,"ya29.eQETFbFOkAs8nWHcmYXKwEi0Zz46NfsrUU_KuQLOLTwWS40y6Fb99aVzEXC0U14m61lcPMIr1hEIBA", "aUAkJG-u6x4RTWuILWy-CA");
} }
private void verifyHash(String accessToken, String expectedAtHash) { @Test
String atHash = HashProvider.oidcHash(Algorithm.RS256, accessToken); public void testAtHashEs() throws Exception {
verifyHash(Algorithm.ES256,"jHkWEdUXMU1BwAsC4vtUsZwnNvTIxEl0z9K3vx5KF0Y", "77QmUPtjPfzWtF2AnpK9RQ");
verifyHash(Algorithm.ES256,"ya29.eQETFbFOkAs8nWHcmYXKwEi0Zz46NfsrUU_KuQLOLTwWS40y6Fb99aVzEXC0U14m61lcPMIr1hEIBA", "aUAkJG-u6x4RTWuILWy-CA");
}
private void verifyHash(String jwtAlgorithm, String accessToken, String expectedAtHash) {
String atHash = HashUtils.oidcHash(jwtAlgorithm, accessToken);
Assert.assertEquals(expectedAtHash, atHash); Assert.assertEquals(expectedAtHash, atHash);
} }
} }

View file

@ -0,0 +1,47 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.crypto;
import java.io.UnsupportedEncodingException;
import org.keycloak.provider.Provider;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface HashProvider extends Provider {
default byte[] hash(String input) throws HashException {
try {
byte[] inputBytes = input.getBytes("UTF-8");
return hash(inputBytes);
} catch (UnsupportedEncodingException e) {
throw new HashException("Unsupported encoding when trying to hash", e);
}
}
byte[] hash(byte[] input) throws HashException;
@Override
default void close() {
}
}

View file

@ -0,0 +1,40 @@
/*
* Copyright 2017 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.Config;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderFactory;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface HashProviderFactory extends ProviderFactory<HashProvider> {
@Override
default void init(Config.Scope config) {
}
@Override
default void postInit(KeycloakSessionFactory factory) {
}
@Override
default void close() {
}
}

View file

@ -0,0 +1,48 @@
/*
* Copyright 2017 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.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class HashSpi implements Spi {
@Override
public boolean isInternal() {
return true;
}
@Override
public String getName() {
return "hash";
}
@Override
public Class<? extends Provider> getProviderClass() {
return HashProvider.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return HashProviderFactory.class;
}
}

View file

@ -73,4 +73,5 @@ org.keycloak.keys.PublicKeyStorageSpi
org.keycloak.keys.KeySpi org.keycloak.keys.KeySpi
org.keycloak.storage.client.ClientStorageProviderSpi org.keycloak.storage.client.ClientStorageProviderSpi
org.keycloak.crypto.SignatureSpi org.keycloak.crypto.SignatureSpi
org.keycloak.crypto.ClientSignatureVerifierSpi org.keycloak.crypto.ClientSignatureVerifierSpi
org.keycloak.crypto.HashSpi

View file

@ -0,0 +1,37 @@
/*
* Copyright 2017 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.jose.jws.crypto.HashUtils;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class JavaAlgorithmHashProvider implements HashProvider {
private final String javaAlgorithm;
public JavaAlgorithmHashProvider(String javaAlgorithm) {
this.javaAlgorithm = javaAlgorithm;
}
@Override
public byte[] hash(byte[] input) throws HashException {
return HashUtils.hash(javaAlgorithm, input);
}
}

View file

@ -0,0 +1,39 @@
/*
* Copyright 2017 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;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class SHA256HashProviderFactory implements HashProviderFactory {
public static final String ID = JavaAlgorithm.SHA256;
@Override
public String getId() {
return ID;
}
@Override
public HashProvider create(KeycloakSession session) {
return new JavaAlgorithmHashProvider(ID);
}
}

View file

@ -0,0 +1,38 @@
/*
* Copyright 2017 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;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class SHA384HashProviderFactory implements HashProviderFactory {
public static final String ID = JavaAlgorithm.SHA384;
@Override
public String getId() {
return ID;
}
@Override
public HashProvider create(KeycloakSession session) {
return new JavaAlgorithmHashProvider(ID);
}
}

View file

@ -0,0 +1,38 @@
/*
* Copyright 2017 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;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class SHA512HashProviderFactory implements HashProviderFactory {
public static final String ID = JavaAlgorithm.SHA512;
@Override
public String getId() {
return ID;
}
@Override
public HashProvider create(KeycloakSession session) {
return new JavaAlgorithmHashProvider(ID);
}
}

View file

@ -25,11 +25,13 @@ import org.keycloak.TokenCategory;
import org.keycloak.cluster.ClusterProvider; import org.keycloak.cluster.ClusterProvider;
import org.keycloak.common.ClientConnection; import org.keycloak.common.ClientConnection;
import org.keycloak.common.util.Time; import org.keycloak.common.util.Time;
import org.keycloak.crypto.HashProvider;
import org.keycloak.crypto.SignatureProvider;
import org.keycloak.events.Details; import org.keycloak.events.Details;
import org.keycloak.events.Errors; import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder; import org.keycloak.events.EventBuilder;
import org.keycloak.jose.jws.JWSInputException; import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.jose.jws.crypto.HashProvider; import org.keycloak.jose.jws.crypto.HashUtils;
import org.keycloak.migration.migrators.MigrationUtils; import org.keycloak.migration.migrators.MigrationUtils;
import org.keycloak.models.AuthenticatedClientSessionModel; import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
@ -763,14 +765,14 @@ public class TokenManager {
} }
public AccessTokenResponseBuilder generateCodeHash(String code) { public AccessTokenResponseBuilder generateCodeHash(String code) {
codeHash = HashProvider.oidcHash(session.tokens().signatureAlgorithm(TokenCategory.ID), code); codeHash = generateOIDCHash(code);
return this; return this;
} }
// 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
public AccessTokenResponseBuilder generateStateHash(String state) { public AccessTokenResponseBuilder generateStateHash(String state) {
stateHash = HashProvider.oidcHash(session.tokens().signatureAlgorithm(TokenCategory.ID), state); stateHash = generateOIDCHash(state);
return this; return this;
} }
@ -801,7 +803,7 @@ public class TokenManager {
} }
if (generateAccessTokenHash) { if (generateAccessTokenHash) {
String atHash = HashProvider.oidcHash(session.tokens().signatureAlgorithm(TokenCategory.ID), res.getToken()); String atHash = generateOIDCHash(res.getToken());
idToken.setAccessTokenHash(atHash); idToken.setAccessTokenHash(atHash);
} }
if (codeHash != null) { if (codeHash != null) {
@ -838,6 +840,18 @@ public class TokenManager {
return res; return res;
} }
private String generateOIDCHash(String input) {
String signatureAlgorithm = session.tokens().signatureAlgorithm(TokenCategory.ID);
SignatureProvider signatureProvider = session.getProvider(SignatureProvider.class, signatureAlgorithm);
String hashAlgorithm = signatureProvider.signer().getHashAlgorithm();
HashProvider hashProvider = session.getProvider(HashProvider.class, hashAlgorithm);
byte[] hash = hashProvider.hash(input);
return HashUtils.encodeHashToOIDC(hash);
}
} }
public class RefreshResult { public class RefreshResult {

View file

@ -0,0 +1,20 @@
#
# Copyright 2017 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.
#
org.keycloak.crypto.SHA256HashProviderFactory
org.keycloak.crypto.SHA384HashProviderFactory
org.keycloak.crypto.SHA512HashProviderFactory

View file

@ -37,13 +37,13 @@ import org.keycloak.OAuth2Constants;
import org.keycloak.OAuthErrorException; import org.keycloak.OAuthErrorException;
import org.keycloak.admin.client.resource.ClientResource; import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.common.util.KeystoreUtil; import org.keycloak.common.util.KeystoreUtil;
import org.keycloak.crypto.Algorithm;
import org.keycloak.events.Details; import org.keycloak.events.Details;
import org.keycloak.events.EventType; import org.keycloak.events.EventType;
import org.keycloak.jose.jws.Algorithm;
import org.keycloak.jose.jws.JWSHeader; import org.keycloak.jose.jws.JWSHeader;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException; import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.jose.jws.crypto.HashProvider; import org.keycloak.jose.jws.crypto.HashUtils;
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
import org.keycloak.protocol.oidc.utils.OIDCResponseType; import org.keycloak.protocol.oidc.utils.OIDCResponseType;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
@ -579,7 +579,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
// Validate "c_hash" // Validate "c_hash"
Assert.assertNull(idToken.getAccessTokenHash()); Assert.assertNull(idToken.getAccessTokenHash());
Assert.assertNotNull(idToken.getCodeHash()); Assert.assertNotNull(idToken.getCodeHash());
Assert.assertEquals(idToken.getCodeHash(), HashProvider.oidcHash(Algorithm.RS256, authzResponse.getCode())); Assert.assertEquals(idToken.getCodeHash(), HashUtils.oidcHash(Algorithm.RS256, authzResponse.getCode()));
// IDToken exchanged for the code // IDToken exchanged for the code
IDToken idToken2 = sendTokenRequestAndGetIDToken(loginEvent); IDToken idToken2 = sendTokenRequestAndGetIDToken(loginEvent);

View file

@ -23,7 +23,6 @@ import org.junit.Test;
import org.keycloak.OAuthErrorException; import org.keycloak.OAuthErrorException;
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.Algorithm;
import org.keycloak.jose.jws.JWSHeader; import org.keycloak.jose.jws.JWSHeader;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.representations.IDToken; import org.keycloak.representations.IDToken;
@ -56,9 +55,6 @@ import static org.junit.Assert.assertNull;
*/ */
public abstract class AbstractOIDCResponseTypeTest extends AbstractTestRealmKeycloakTest { public abstract class AbstractOIDCResponseTypeTest extends AbstractTestRealmKeycloakTest {
// Harcoded for now
Algorithm jwsAlgorithm = Algorithm.RS256;
@Rule @Rule
public AssertEvents events = new AssertEvents(this); public AssertEvents events = new AssertEvents(this);
@ -263,6 +259,20 @@ public abstract class AbstractOIDCResponseTypeTest extends AbstractTestRealmKeyc
TokenSignatureUtil.changeClientIdTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), "RS256"); TokenSignatureUtil.changeClientIdTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), "RS256");
} }
} }
@Test
public void oidcFlow_RealmES256_ClientES384_EffectiveES384() throws Exception {
try {
setSignatureAlgorithm("ES384");
TokenSignatureUtil.changeRealmTokenSignatureProvider(adminClient, "ES256");
TokenSignatureUtil.changeClientIdTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), "ES384");
oidcFlow("ES256", "ES384");
} finally {
setSignatureAlgorithm("RS256");
TokenSignatureUtil.changeClientIdTokenSignatureProvider(ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"), "RS256");
}
}
private String sigAlgName = "RS256"; private String sigAlgName = "RS256";
private void setSignatureAlgorithm(String sigAlgName) { private void setSignatureAlgorithm(String sigAlgName) {
this.sigAlgName = sigAlgName; this.sigAlgName = sigAlgName;

View file

@ -20,7 +20,7 @@ package org.keycloak.testsuite.oidc.flows;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.keycloak.events.Details; import org.keycloak.events.Details;
import org.keycloak.jose.jws.crypto.HashProvider; import org.keycloak.jose.jws.crypto.HashUtils;
import org.keycloak.protocol.oidc.utils.OIDCResponseType; import org.keycloak.protocol.oidc.utils.OIDCResponseType;
import org.keycloak.representations.IDToken; import org.keycloak.representations.IDToken;
import org.keycloak.representations.idm.EventRepresentation; import org.keycloak.representations.idm.EventRepresentation;
@ -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(), HashProvider.oidcHash(getSignatureAlgorithm(), authzResponse.getCode())); Assert.assertEquals(idToken.getCodeHash(), HashUtils.oidcHash(getSignatureAlgorithm(), 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(), HashProvider.oidcHash(getSignatureAlgorithm(), authzResponse.getState())); Assert.assertEquals(idToken.getStateHash(), HashUtils.oidcHash(getSignatureAlgorithm(), authzResponse.getState()));
// IDToken exchanged for the code // IDToken exchanged for the code
IDToken idToken2 = sendTokenRequestAndGetIDToken(loginEvent); IDToken idToken2 = sendTokenRequestAndGetIDToken(loginEvent);

View file

@ -20,7 +20,7 @@ package org.keycloak.testsuite.oidc.flows;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.keycloak.events.Details; import org.keycloak.events.Details;
import org.keycloak.jose.jws.crypto.HashProvider; import org.keycloak.jose.jws.crypto.HashUtils;
import org.keycloak.protocol.oidc.utils.OIDCResponseType; import org.keycloak.protocol.oidc.utils.OIDCResponseType;
import org.keycloak.representations.IDToken; import org.keycloak.representations.IDToken;
import org.keycloak.representations.idm.EventRepresentation; import org.keycloak.representations.idm.EventRepresentation;
@ -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(), HashProvider.oidcHash(getSignatureAlgorithm(), authzResponse.getAccessToken())); Assert.assertEquals(idToken.getAccessTokenHash(), HashUtils.oidcHash(getSignatureAlgorithm(), authzResponse.getAccessToken()));
Assert.assertNotNull(idToken.getCodeHash()); Assert.assertNotNull(idToken.getCodeHash());
Assert.assertEquals(idToken.getCodeHash(), HashProvider.oidcHash(getSignatureAlgorithm(), authzResponse.getCode())); Assert.assertEquals(idToken.getCodeHash(), HashUtils.oidcHash(getSignatureAlgorithm(), 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(), HashProvider.oidcHash(getSignatureAlgorithm(), authzResponse.getState())); Assert.assertEquals(idToken.getStateHash(), HashUtils.oidcHash(getSignatureAlgorithm(), authzResponse.getState()));
// IDToken exchanged for the code // IDToken exchanged for the code
IDToken idToken2 = sendTokenRequestAndGetIDToken(loginEvent); IDToken idToken2 = sendTokenRequestAndGetIDToken(loginEvent);

View file

@ -20,7 +20,7 @@ package org.keycloak.testsuite.oidc.flows;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.keycloak.events.Details; import org.keycloak.events.Details;
import org.keycloak.jose.jws.crypto.HashProvider; import org.keycloak.jose.jws.crypto.HashUtils;
import org.keycloak.protocol.oidc.utils.OIDCResponseType; import org.keycloak.protocol.oidc.utils.OIDCResponseType;
import org.keycloak.representations.IDToken; import org.keycloak.representations.IDToken;
import org.keycloak.representations.idm.EventRepresentation; import org.keycloak.representations.idm.EventRepresentation;
@ -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(), HashProvider.oidcHash(getSignatureAlgorithm(), authzResponse.getAccessToken())); Assert.assertEquals(idToken.getAccessTokenHash(), HashUtils.oidcHash(getSignatureAlgorithm(), authzResponse.getAccessToken()));
Assert.assertNull(idToken.getCodeHash()); Assert.assertNull(idToken.getCodeHash());
return Collections.singletonList(idToken); return Collections.singletonList(idToken);