KEYCLOAK-9089 IllegalArgumentException when trying to use ES256 as OIDC access token signature
This commit is contained in:
parent
4150daa9cb
commit
061693a8c9
22 changed files with 472 additions and 61 deletions
|
@ -37,6 +37,11 @@ public class AsymmetricSignatureSignerContext implements SignatureSignerContext
|
|||
return key.getAlgorithm();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHashAlgorithm() {
|
||||
return JavaAlgorithm.getJavaAlgorithmForHash(key.getAlgorithm());
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] sign(byte[] data) throws SignatureException {
|
||||
try {
|
||||
|
|
32
core/src/main/java/org/keycloak/crypto/HashException.java
Normal file
32
core/src/main/java/org/keycloak/crypto/HashException.java
Normal 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);
|
||||
}
|
||||
}
|
|
@ -29,6 +29,10 @@ public class JavaAlgorithm {
|
|||
public static final String ES512 = "SHA512withECDSA";
|
||||
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) {
|
||||
switch (algorithm) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -36,6 +36,11 @@ public class MacSignatureSignerContext implements SignatureSignerContext {
|
|||
return key.getAlgorithm();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHashAlgorithm() {
|
||||
return JavaAlgorithm.getJavaAlgorithmForHash(key.getAlgorithm());
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] sign(byte[] data) throws SignatureException {
|
||||
try {
|
||||
|
|
|
@ -22,6 +22,8 @@ public interface SignatureSignerContext {
|
|||
|
||||
String getAlgorithm();
|
||||
|
||||
String getHashAlgorithm();
|
||||
|
||||
byte[] sign(byte[] data) throws SignatureException;
|
||||
|
||||
}
|
||||
|
|
|
@ -18,51 +18,48 @@
|
|||
package org.keycloak.jose.jws.crypto;
|
||||
|
||||
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.util.Arrays;
|
||||
|
||||
/**
|
||||
* @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) {
|
||||
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;
|
||||
byte[] hashInput = Arrays.copyOf(digest, hashLength);
|
||||
return encodeHashToOIDC(hash);
|
||||
} 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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
|
@ -20,8 +20,8 @@ package org.keycloak;
|
|||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.jose.jws.Algorithm;
|
||||
import org.keycloak.jose.jws.crypto.HashProvider;
|
||||
import org.keycloak.crypto.Algorithm;
|
||||
import org.keycloak.jose.jws.crypto.HashUtils;
|
||||
|
||||
import java.security.Security;
|
||||
|
||||
|
@ -37,13 +37,19 @@ public class AtHashTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testAtHash() throws Exception {
|
||||
verifyHash("jHkWEdUXMU1BwAsC4vtUsZwnNvTIxEl0z9K3vx5KF0Y", "77QmUPtjPfzWtF2AnpK9RQ");
|
||||
verifyHash("ya29.eQETFbFOkAs8nWHcmYXKwEi0Zz46NfsrUU_KuQLOLTwWS40y6Fb99aVzEXC0U14m61lcPMIr1hEIBA", "aUAkJG-u6x4RTWuILWy-CA");
|
||||
public void testAtHashRsa() throws Exception {
|
||||
verifyHash(Algorithm.RS256,"jHkWEdUXMU1BwAsC4vtUsZwnNvTIxEl0z9K3vx5KF0Y", "77QmUPtjPfzWtF2AnpK9RQ");
|
||||
verifyHash(Algorithm.RS256,"ya29.eQETFbFOkAs8nWHcmYXKwEi0Zz46NfsrUU_KuQLOLTwWS40y6Fb99aVzEXC0U14m61lcPMIr1hEIBA", "aUAkJG-u6x4RTWuILWy-CA");
|
||||
}
|
||||
|
||||
private void verifyHash(String accessToken, String expectedAtHash) {
|
||||
String atHash = HashProvider.oidcHash(Algorithm.RS256, accessToken);
|
||||
@Test
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
}
|
||||
|
||||
}
|
|
@ -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() {
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -73,4 +73,5 @@ org.keycloak.keys.PublicKeyStorageSpi
|
|||
org.keycloak.keys.KeySpi
|
||||
org.keycloak.storage.client.ClientStorageProviderSpi
|
||||
org.keycloak.crypto.SignatureSpi
|
||||
org.keycloak.crypto.ClientSignatureVerifierSpi
|
||||
org.keycloak.crypto.ClientSignatureVerifierSpi
|
||||
org.keycloak.crypto.HashSpi
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -25,11 +25,13 @@ import org.keycloak.TokenCategory;
|
|||
import org.keycloak.cluster.ClusterProvider;
|
||||
import org.keycloak.common.ClientConnection;
|
||||
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.Errors;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
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.models.AuthenticatedClientSessionModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
|
@ -763,14 +765,14 @@ public class TokenManager {
|
|||
}
|
||||
|
||||
public AccessTokenResponseBuilder generateCodeHash(String code) {
|
||||
codeHash = HashProvider.oidcHash(session.tokens().signatureAlgorithm(TokenCategory.ID), code);
|
||||
codeHash = generateOIDCHash(code);
|
||||
return this;
|
||||
}
|
||||
|
||||
// Financial API - Part 2: Read and Write API Security Profile
|
||||
// http://openid.net/specs/openid-financial-api-part-2.html#authorization-server
|
||||
public AccessTokenResponseBuilder generateStateHash(String state) {
|
||||
stateHash = HashProvider.oidcHash(session.tokens().signatureAlgorithm(TokenCategory.ID), state);
|
||||
stateHash = generateOIDCHash(state);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -801,7 +803,7 @@ public class TokenManager {
|
|||
}
|
||||
|
||||
if (generateAccessTokenHash) {
|
||||
String atHash = HashProvider.oidcHash(session.tokens().signatureAlgorithm(TokenCategory.ID), res.getToken());
|
||||
String atHash = generateOIDCHash(res.getToken());
|
||||
idToken.setAccessTokenHash(atHash);
|
||||
}
|
||||
if (codeHash != null) {
|
||||
|
@ -838,6 +840,18 @@ public class TokenManager {
|
|||
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 {
|
||||
|
|
|
@ -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
|
|
@ -37,13 +37,13 @@ import org.keycloak.OAuth2Constants;
|
|||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.common.util.KeystoreUtil;
|
||||
import org.keycloak.crypto.Algorithm;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.jose.jws.Algorithm;
|
||||
import org.keycloak.jose.jws.JWSHeader;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
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.utils.OIDCResponseType;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
|
@ -579,7 +579,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
|
|||
// Validate "c_hash"
|
||||
Assert.assertNull(idToken.getAccessTokenHash());
|
||||
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 idToken2 = sendTokenRequestAndGetIDToken(loginEvent);
|
||||
|
|
|
@ -23,7 +23,6 @@ import org.junit.Test;
|
|||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.jose.jws.Algorithm;
|
||||
import org.keycloak.jose.jws.JWSHeader;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.representations.IDToken;
|
||||
|
@ -56,9 +55,6 @@ import static org.junit.Assert.assertNull;
|
|||
*/
|
||||
public abstract class AbstractOIDCResponseTypeTest extends AbstractTestRealmKeycloakTest {
|
||||
|
||||
// Harcoded for now
|
||||
Algorithm jwsAlgorithm = Algorithm.RS256;
|
||||
|
||||
@Rule
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
@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 void setSignatureAlgorithm(String sigAlgName) {
|
||||
this.sigAlgName = sigAlgName;
|
||||
|
|
|
@ -20,7 +20,7 @@ package org.keycloak.testsuite.oidc.flows;
|
|||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
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.representations.IDToken;
|
||||
import org.keycloak.representations.idm.EventRepresentation;
|
||||
|
@ -64,14 +64,14 @@ public class OIDCHybridResponseTypeCodeIDTokenTest extends AbstractOIDCResponseT
|
|||
Assert.assertNull(idToken.getAccessTokenHash());
|
||||
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
|
||||
// http://openid.net/specs/openid-financial-api-part-2.html#authorization-server
|
||||
// Validate "s_hash"
|
||||
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 idToken2 = sendTokenRequestAndGetIDToken(loginEvent);
|
||||
|
|
|
@ -20,7 +20,7 @@ package org.keycloak.testsuite.oidc.flows;
|
|||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
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.representations.IDToken;
|
||||
import org.keycloak.representations.idm.EventRepresentation;
|
||||
|
@ -63,17 +63,17 @@ public class OIDCHybridResponseTypeCodeIDTokenTokenTest extends AbstractOIDCResp
|
|||
// Validate "at_hash" and "c_hash"
|
||||
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.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
|
||||
// http://openid.net/specs/openid-financial-api-part-2.html#authorization-server
|
||||
// Validate "s_hash"
|
||||
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 idToken2 = sendTokenRequestAndGetIDToken(loginEvent);
|
||||
|
|
|
@ -20,7 +20,7 @@ package org.keycloak.testsuite.oidc.flows;
|
|||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
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.representations.IDToken;
|
||||
import org.keycloak.representations.idm.EventRepresentation;
|
||||
|
@ -62,7 +62,7 @@ public class OIDCImplicitResponseTypeIDTokenTokenTest extends AbstractOIDCRespon
|
|||
// Validate "at_hash"
|
||||
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());
|
||||
|
||||
return Collections.singletonList(idToken);
|
||||
|
|
Loading…
Reference in a new issue