diff --git a/core/src/main/java/org/keycloak/jose/jwk/ECPublicJWK.java b/core/src/main/java/org/keycloak/jose/jwk/ECPublicJWK.java index 19644aa2598..d97c7e8cfa5 100644 --- a/core/src/main/java/org/keycloak/jose/jwk/ECPublicJWK.java +++ b/core/src/main/java/org/keycloak/jose/jwk/ECPublicJWK.java @@ -24,6 +24,8 @@ import com.fasterxml.jackson.annotation.JsonProperty; */ public class ECPublicJWK extends JWK { + public static final String EC = "EC"; + public static final String CRV = "crv"; public static final String X = "x"; public static final String Y = "y"; diff --git a/core/src/main/java/org/keycloak/jose/jwk/JWKParser.java b/core/src/main/java/org/keycloak/jose/jwk/JWKParser.java index b27c48c2ff0..db59973f537 100755 --- a/core/src/main/java/org/keycloak/jose/jwk/JWKParser.java +++ b/core/src/main/java/org/keycloak/jose/jwk/JWKParser.java @@ -128,7 +128,7 @@ public class JWKParser { } public boolean isKeyTypeSupported(String keyType) { - return RSAPublicJWK.RSA.equals(keyType); + return (RSAPublicJWK.RSA.equals(keyType) || ECPublicJWK.EC.equals(keyType)); } } diff --git a/core/src/main/java/org/keycloak/util/JWKSUtils.java b/core/src/main/java/org/keycloak/util/JWKSUtils.java index 8db20e854ce..1745239129a 100644 --- a/core/src/main/java/org/keycloak/util/JWKSUtils.java +++ b/core/src/main/java/org/keycloak/util/JWKSUtils.java @@ -17,6 +17,8 @@ package org.keycloak.util; +import org.keycloak.crypto.KeyUse; +import org.keycloak.crypto.KeyWrapper; import org.keycloak.jose.jwk.JSONWebKeySet; import org.keycloak.jose.jwk.JWK; import org.keycloak.jose.jwk.JWKParser; @@ -43,6 +45,34 @@ public class JWKSUtils { return result; } + public static Map getKeyWrappersForUse(JSONWebKeySet keySet, JWK.Use requestedUse) { + Map result = new HashMap<>(); + for (JWK jwk : keySet.getKeys()) { + JWKParser parser = JWKParser.create(jwk); + if (jwk.getPublicKeyUse().equals(requestedUse.asString()) && parser.isKeyTypeSupported(jwk.getKeyType())) { + KeyWrapper keyWrapper = new KeyWrapper(); + keyWrapper.setKid(jwk.getKeyId()); + keyWrapper.setAlgorithm(jwk.getAlgorithm()); + keyWrapper.setType(jwk.getKeyType()); + keyWrapper.setUse(getKeyUse(jwk.getPublicKeyUse())); + keyWrapper.setVerifyKey(parser.toPublicKey()); + result.put(keyWrapper.getKid(), keyWrapper); + } + } + return result; + } + + private static KeyUse getKeyUse(String keyUse) { + switch (keyUse) { + case "sig" : + return KeyUse.SIG; + case "enc" : + return KeyUse.ENC; + default : + return null; + } + } + public static JWK getKeyForUse(JSONWebKeySet keySet, JWK.Use requestedUse) { for (JWK jwk : keySet.getKeys()) { JWKParser parser = JWKParser.create(jwk); diff --git a/model/infinispan/src/main/java/org/keycloak/keys/infinispan/InfinispanPublicKeyStorageProvider.java b/model/infinispan/src/main/java/org/keycloak/keys/infinispan/InfinispanPublicKeyStorageProvider.java index 933b92352b4..230baec61cc 100644 --- a/model/infinispan/src/main/java/org/keycloak/keys/infinispan/InfinispanPublicKeyStorageProvider.java +++ b/model/infinispan/src/main/java/org/keycloak/keys/infinispan/InfinispanPublicKeyStorageProvider.java @@ -17,7 +17,6 @@ package org.keycloak.keys.infinispan; -import java.security.PublicKey; import java.util.Collections; import java.util.HashSet; import java.util.Map; @@ -30,6 +29,7 @@ import org.infinispan.Cache; import org.jboss.logging.Logger; import org.keycloak.cluster.ClusterProvider; import org.keycloak.common.util.Time; +import org.keycloak.crypto.KeyWrapper; import org.keycloak.keys.PublicKeyLoader; import org.keycloak.keys.PublicKeyStorageProvider; import org.keycloak.models.KeycloakSession; @@ -127,11 +127,11 @@ public class InfinispanPublicKeyStorageProvider implements PublicKeyStorageProvi @Override - public PublicKey getPublicKey(String modelKey, String kid, PublicKeyLoader loader) { + public KeyWrapper getPublicKey(String modelKey, String kid, PublicKeyLoader loader) { // Check if key is in cache PublicKeysEntry entry = keys.get(modelKey); if (entry != null) { - PublicKey publicKey = getPublicKey(entry.getCurrentKeys(), kid); + KeyWrapper publicKey = getPublicKey(entry.getCurrentKeys(), kid); if (publicKey != null) { return publicKey; } @@ -157,7 +157,7 @@ public class InfinispanPublicKeyStorageProvider implements PublicKeyStorageProvi entry = task.get(); // Computation finished. Let's see if key is available - PublicKey publicKey = getPublicKey(entry.getCurrentKeys(), kid); + KeyWrapper publicKey = getPublicKey(entry.getCurrentKeys(), kid); if (publicKey != null) { return publicKey; } @@ -182,7 +182,7 @@ public class InfinispanPublicKeyStorageProvider implements PublicKeyStorageProvi return null; } - private PublicKey getPublicKey(Map publicKeys, String kid) { + private KeyWrapper getPublicKey(Map publicKeys, String kid) { // Backwards compatibility if (kid == null && !publicKeys.isEmpty()) { return publicKeys.values().iterator().next(); @@ -218,7 +218,7 @@ public class InfinispanPublicKeyStorageProvider implements PublicKeyStorageProvi // Check again if we are allowed to send request. There is a chance other task was already finished and removed from tasksInProgress in the meantime. if (currentTime > lastRequestTime + minTimeBetweenRequests) { - Map publicKeys = delegate.loadKeys(); + Map publicKeys = delegate.loadKeys(); if (log.isDebugEnabled()) { log.debugf("Public keys retrieved successfully for model %s. New kids: %s", modelKey, publicKeys.keySet().toString()); diff --git a/model/infinispan/src/main/java/org/keycloak/keys/infinispan/PublicKeysEntry.java b/model/infinispan/src/main/java/org/keycloak/keys/infinispan/PublicKeysEntry.java index cb0561cc147..2f2d807de74 100644 --- a/model/infinispan/src/main/java/org/keycloak/keys/infinispan/PublicKeysEntry.java +++ b/model/infinispan/src/main/java/org/keycloak/keys/infinispan/PublicKeysEntry.java @@ -18,9 +18,10 @@ package org.keycloak.keys.infinispan; import java.io.Serializable; -import java.security.PublicKey; import java.util.Map; +import org.keycloak.crypto.KeyWrapper; + /** * @author Marek Posolda */ @@ -28,9 +29,9 @@ public class PublicKeysEntry implements Serializable { private final int lastRequestTime; - private final Map currentKeys; + private final Map currentKeys; - public PublicKeysEntry(int lastRequestTime, Map currentKeys) { + public PublicKeysEntry(int lastRequestTime, Map currentKeys) { this.lastRequestTime = lastRequestTime; this.currentKeys = currentKeys; } @@ -39,7 +40,7 @@ public class PublicKeysEntry implements Serializable { return lastRequestTime; } - public Map getCurrentKeys() { + public Map getCurrentKeys() { return currentKeys; } } diff --git a/model/infinispan/src/test/java/org/keycloak/keys/infinispan/InfinispanKeyStorageProviderTest.java b/model/infinispan/src/test/java/org/keycloak/keys/infinispan/InfinispanKeyStorageProviderTest.java index 308d7b2e14e..b68d6537eef 100644 --- a/model/infinispan/src/test/java/org/keycloak/keys/infinispan/InfinispanKeyStorageProviderTest.java +++ b/model/infinispan/src/test/java/org/keycloak/keys/infinispan/InfinispanKeyStorageProviderTest.java @@ -17,7 +17,6 @@ package org.keycloak.keys.infinispan; -import java.security.PublicKey; import java.util.Collections; import java.util.LinkedList; import java.util.List; @@ -39,6 +38,7 @@ import org.junit.Before; import org.junit.Test; import org.keycloak.common.util.Time; import org.keycloak.connections.infinispan.InfinispanConnectionProvider; +import org.keycloak.crypto.KeyWrapper; import org.keycloak.keys.PublicKeyLoader; /** @@ -144,7 +144,7 @@ public class InfinispanKeyStorageProviderTest { } @Override - public Map loadKeys() throws Exception { + public Map loadKeys() throws Exception { counters.putIfAbsent(modelKey, new AtomicInteger(0)); AtomicInteger currentCounter = counters.get(modelKey); diff --git a/server-spi-private/src/main/java/org/keycloak/crypto/ClientSignatureVerifierProvider.java b/server-spi-private/src/main/java/org/keycloak/crypto/ClientSignatureVerifierProvider.java new file mode 100644 index 00000000000..a4224cbdc7c --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/crypto/ClientSignatureVerifierProvider.java @@ -0,0 +1,31 @@ +/* + * Copyright 2016 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.common.VerificationException; +import org.keycloak.jose.jws.JWSInput; +import org.keycloak.models.ClientModel; +import org.keycloak.provider.Provider; + +public interface ClientSignatureVerifierProvider extends Provider { + SignatureVerifierContext verifier(ClientModel client, JWSInput input) throws VerificationException; + + @Override + default void close() { + } +} diff --git a/server-spi-private/src/main/java/org/keycloak/crypto/ClientSignatureVerifierProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/crypto/ClientSignatureVerifierProviderFactory.java new file mode 100644 index 00000000000..d8fd4cdb19c --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/crypto/ClientSignatureVerifierProviderFactory.java @@ -0,0 +1,38 @@ +/* + * Copyright 2016 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; + +public interface ClientSignatureVerifierProviderFactory extends ProviderFactory { + + @Override + default void init(Config.Scope config) { + } + + @Override + default void postInit(KeycloakSessionFactory factory) { + } + + @Override + default void close() { + } + +} diff --git a/server-spi-private/src/main/java/org/keycloak/crypto/ClientSignatureVerifierSpi.java b/server-spi-private/src/main/java/org/keycloak/crypto/ClientSignatureVerifierSpi.java new file mode 100644 index 00000000000..0461f859f54 --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/crypto/ClientSignatureVerifierSpi.java @@ -0,0 +1,44 @@ +/* + * Copyright 2016 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; + +public class ClientSignatureVerifierSpi implements Spi { + + @Override + public boolean isInternal() { + return true; + } + + @Override + public String getName() { + return "clientSignature"; + } + + @Override + public Class getProviderClass() { + return ClientSignatureVerifierProvider.class; + } + + @Override + public Class getProviderFactoryClass() { + return ClientSignatureVerifierProviderFactory.class; + } +} diff --git a/server-spi-private/src/main/java/org/keycloak/keys/PublicKeyLoader.java b/server-spi-private/src/main/java/org/keycloak/keys/PublicKeyLoader.java index c4ec6720c2a..3ec53f8bb77 100644 --- a/server-spi-private/src/main/java/org/keycloak/keys/PublicKeyLoader.java +++ b/server-spi-private/src/main/java/org/keycloak/keys/PublicKeyLoader.java @@ -17,14 +17,15 @@ package org.keycloak.keys; -import java.security.PublicKey; import java.util.Map; +import org.keycloak.crypto.KeyWrapper; + /** * @author Marek Posolda */ public interface PublicKeyLoader { - Map loadKeys() throws Exception; + Map loadKeys() throws Exception; } diff --git a/server-spi-private/src/main/java/org/keycloak/keys/PublicKeyStorageProvider.java b/server-spi-private/src/main/java/org/keycloak/keys/PublicKeyStorageProvider.java index 1d721806790..437ad239ab6 100644 --- a/server-spi-private/src/main/java/org/keycloak/keys/PublicKeyStorageProvider.java +++ b/server-spi-private/src/main/java/org/keycloak/keys/PublicKeyStorageProvider.java @@ -17,8 +17,7 @@ package org.keycloak.keys; -import java.security.PublicKey; - +import org.keycloak.crypto.KeyWrapper; import org.keycloak.provider.Provider; /** @@ -35,7 +34,7 @@ public interface PublicKeyStorageProvider extends Provider { * @param loader * @return */ - PublicKey getPublicKey(String modelKey, String kid, PublicKeyLoader loader); + KeyWrapper getPublicKey(String modelKey, String kid, PublicKeyLoader loader); /** * Clears all the cached public keys, so they need to be loaded again diff --git a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi index 751b8cce924..560d15a579b 100755 --- a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi @@ -72,4 +72,5 @@ org.keycloak.credential.CredentialSpi org.keycloak.keys.PublicKeyStorageSpi org.keycloak.keys.KeySpi org.keycloak.storage.client.ClientStorageProviderSpi -org.keycloak.crypto.SignatureSpi \ No newline at end of file +org.keycloak.crypto.SignatureSpi +org.keycloak.crypto.ClientSignatureVerifierSpi \ No newline at end of file diff --git a/server-spi/src/main/java/org/keycloak/models/TokenManager.java b/server-spi/src/main/java/org/keycloak/models/TokenManager.java index 4666e576fca..bd0244f71f5 100644 --- a/server-spi/src/main/java/org/keycloak/models/TokenManager.java +++ b/server-spi/src/main/java/org/keycloak/models/TokenManager.java @@ -41,4 +41,6 @@ public interface TokenManager { String signatureAlgorithm(TokenCategory category); + T decodeClientJWT(String token, ClientModel client, Class clazz); + } diff --git a/services/src/main/java/org/keycloak/crypto/AsymmetricClientSignatureVerifierProvider.java b/services/src/main/java/org/keycloak/crypto/AsymmetricClientSignatureVerifierProvider.java new file mode 100644 index 00000000000..fc549451a81 --- /dev/null +++ b/services/src/main/java/org/keycloak/crypto/AsymmetricClientSignatureVerifierProvider.java @@ -0,0 +1,38 @@ +/* + * Copyright 2016 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.common.VerificationException; +import org.keycloak.jose.jws.JWSInput; +import org.keycloak.models.ClientModel; +import org.keycloak.models.KeycloakSession; + +public class AsymmetricClientSignatureVerifierProvider implements ClientSignatureVerifierProvider { + private final KeycloakSession session; + private final String algorithm; + + public AsymmetricClientSignatureVerifierProvider(KeycloakSession session, String algorithm) { + this.session = session; + this.algorithm = algorithm; + } + + @Override + public SignatureVerifierContext verifier(ClientModel client, JWSInput input) throws VerificationException { + return new ClientAsymmetricSignatureVerifierContext(session, client, input); + } + +} diff --git a/services/src/main/java/org/keycloak/crypto/ClientAsymmetricSignatureVerifierContext.java b/services/src/main/java/org/keycloak/crypto/ClientAsymmetricSignatureVerifierContext.java new file mode 100644 index 00000000000..317c3630f4d --- /dev/null +++ b/services/src/main/java/org/keycloak/crypto/ClientAsymmetricSignatureVerifierContext.java @@ -0,0 +1,38 @@ +/* + * Copyright 2016 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.common.VerificationException; +import org.keycloak.jose.jws.JWSInput; +import org.keycloak.keys.loader.PublicKeyStorageManager; +import org.keycloak.models.ClientModel; +import org.keycloak.models.KeycloakSession; + +public class ClientAsymmetricSignatureVerifierContext extends AsymmetricSignatureVerifierContext { + + public ClientAsymmetricSignatureVerifierContext(KeycloakSession session, ClientModel client, JWSInput input) throws VerificationException { + super(getKey(session, client, input)); + } + + private static KeyWrapper getKey(KeycloakSession session, ClientModel client, JWSInput input) throws VerificationException { + KeyWrapper key = PublicKeyStorageManager.getClientPublicKeyWrapper(session, client, input); + if (key == null) { + throw new VerificationException("Key not found"); + } + return key; + } +} diff --git a/services/src/main/java/org/keycloak/crypto/ES256ClientSignatureVerifierProviderFactory.java b/services/src/main/java/org/keycloak/crypto/ES256ClientSignatureVerifierProviderFactory.java new file mode 100644 index 00000000000..c03f040652b --- /dev/null +++ b/services/src/main/java/org/keycloak/crypto/ES256ClientSignatureVerifierProviderFactory.java @@ -0,0 +1,35 @@ +/* + * Copyright 2016 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 ES256ClientSignatureVerifierProviderFactory implements ClientSignatureVerifierProviderFactory { + + public static final String ID = Algorithm.ES256; + + @Override + public String getId() { + return ID; + } + + @Override + public ClientSignatureVerifierProvider create(KeycloakSession session) { + return new AsymmetricClientSignatureVerifierProvider(session, Algorithm.ES256); + } + +} diff --git a/services/src/main/java/org/keycloak/crypto/ES384ClientSignatureVerifierProviderFactory.java b/services/src/main/java/org/keycloak/crypto/ES384ClientSignatureVerifierProviderFactory.java new file mode 100644 index 00000000000..715daf5c77a --- /dev/null +++ b/services/src/main/java/org/keycloak/crypto/ES384ClientSignatureVerifierProviderFactory.java @@ -0,0 +1,35 @@ +/* + * Copyright 2016 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 ES384ClientSignatureVerifierProviderFactory implements ClientSignatureVerifierProviderFactory { + + public static final String ID = Algorithm.ES384; + + @Override + public String getId() { + return ID; + } + + @Override + public ClientSignatureVerifierProvider create(KeycloakSession session) { + return new AsymmetricClientSignatureVerifierProvider(session, Algorithm.ES384); + } + +} diff --git a/services/src/main/java/org/keycloak/crypto/ES512ClientSignatureVerifierProviderFactory.java b/services/src/main/java/org/keycloak/crypto/ES512ClientSignatureVerifierProviderFactory.java new file mode 100644 index 00000000000..cb766570183 --- /dev/null +++ b/services/src/main/java/org/keycloak/crypto/ES512ClientSignatureVerifierProviderFactory.java @@ -0,0 +1,35 @@ +/* + * Copyright 2016 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 ES512ClientSignatureVerifierProviderFactory implements ClientSignatureVerifierProviderFactory { + + public static final String ID = Algorithm.ES512; + + @Override + public String getId() { + return ID; + } + + @Override + public ClientSignatureVerifierProvider create(KeycloakSession session) { + return new AsymmetricClientSignatureVerifierProvider(session, Algorithm.ES512); + } + +} diff --git a/services/src/main/java/org/keycloak/crypto/RS256ClientSignatureVerifierProviderFactory.java b/services/src/main/java/org/keycloak/crypto/RS256ClientSignatureVerifierProviderFactory.java new file mode 100644 index 00000000000..5fc1a74ae28 --- /dev/null +++ b/services/src/main/java/org/keycloak/crypto/RS256ClientSignatureVerifierProviderFactory.java @@ -0,0 +1,35 @@ +/* + * Copyright 2016 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 RS256ClientSignatureVerifierProviderFactory implements ClientSignatureVerifierProviderFactory { + + public static final String ID = Algorithm.RS256; + + @Override + public String getId() { + return ID; + } + + @Override + public ClientSignatureVerifierProvider create(KeycloakSession session) { + return new AsymmetricClientSignatureVerifierProvider(session, Algorithm.RS256); + } + +} diff --git a/services/src/main/java/org/keycloak/crypto/RS384ClientSignatureVerifierProviderFactory.java b/services/src/main/java/org/keycloak/crypto/RS384ClientSignatureVerifierProviderFactory.java new file mode 100644 index 00000000000..6c1f5c0b0ee --- /dev/null +++ b/services/src/main/java/org/keycloak/crypto/RS384ClientSignatureVerifierProviderFactory.java @@ -0,0 +1,35 @@ +/* + * Copyright 2016 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 RS384ClientSignatureVerifierProviderFactory implements ClientSignatureVerifierProviderFactory { + + public static final String ID = Algorithm.RS384; + + @Override + public String getId() { + return ID; + } + + @Override + public ClientSignatureVerifierProvider create(KeycloakSession session) { + return new AsymmetricClientSignatureVerifierProvider(session, Algorithm.RS384); + } + +} diff --git a/services/src/main/java/org/keycloak/crypto/RS512ClientSignatureVerifierProviderFactory.java b/services/src/main/java/org/keycloak/crypto/RS512ClientSignatureVerifierProviderFactory.java new file mode 100644 index 00000000000..efeedb1c20c --- /dev/null +++ b/services/src/main/java/org/keycloak/crypto/RS512ClientSignatureVerifierProviderFactory.java @@ -0,0 +1,35 @@ +/* + * Copyright 2016 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 RS512ClientSignatureVerifierProviderFactory implements ClientSignatureVerifierProviderFactory { + + public static final String ID = Algorithm.RS512; + + @Override + public String getId() { + return ID; + } + + @Override + public ClientSignatureVerifierProvider create(KeycloakSession session) { + return new AsymmetricClientSignatureVerifierProvider(session, Algorithm.RS512); + } + +} diff --git a/services/src/main/java/org/keycloak/jose/jws/DefaultTokenManager.java b/services/src/main/java/org/keycloak/jose/jws/DefaultTokenManager.java index 9a0323d1118..df9c5e14ee9 100644 --- a/services/src/main/java/org/keycloak/jose/jws/DefaultTokenManager.java +++ b/services/src/main/java/org/keycloak/jose/jws/DefaultTokenManager.java @@ -20,6 +20,7 @@ import org.jboss.logging.Logger; import org.keycloak.Token; import org.keycloak.TokenCategory; import org.keycloak.crypto.Algorithm; +import org.keycloak.crypto.ClientSignatureVerifierProvider; import org.keycloak.crypto.KeyUse; import org.keycloak.crypto.SignatureProvider; import org.keycloak.crypto.SignatureSignerContext; @@ -83,6 +84,29 @@ public class DefaultTokenManager implements TokenManager { } } + @Override + public T decodeClientJWT(String token, ClientModel client, Class clazz) { + if (token == null) { + return null; + } + try { + JWSInput jws = new JWSInput(token); + + String signatureAlgorithm = jws.getHeader().getAlgorithm().name(); + + ClientSignatureVerifierProvider signatureProvider = session.getProvider(ClientSignatureVerifierProvider.class, signatureAlgorithm); + if (signatureProvider == null) { + return null; + } + + boolean valid = signatureProvider.verifier(client, jws).verify(jws.getEncodedSignatureInput().getBytes("UTF-8"), jws.getSignature()); + return valid ? jws.readJsonContent(clazz) : null; + } catch (Exception e) { + logger.debug("Failed to decode token", e); + return null; + } + } + @Override public String signatureAlgorithm(TokenCategory category) { switch (category) { diff --git a/services/src/main/java/org/keycloak/keys/loader/ClientPublicKeyLoader.java b/services/src/main/java/org/keycloak/keys/loader/ClientPublicKeyLoader.java index af2a99c2519..23f43d73db9 100644 --- a/services/src/main/java/org/keycloak/keys/loader/ClientPublicKeyLoader.java +++ b/services/src/main/java/org/keycloak/keys/loader/ClientPublicKeyLoader.java @@ -20,6 +20,10 @@ package org.keycloak.keys.loader; import org.jboss.logging.Logger; import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator; import org.keycloak.common.util.KeyUtils; +import org.keycloak.crypto.Algorithm; +import org.keycloak.crypto.KeyType; +import org.keycloak.crypto.KeyUse; +import org.keycloak.crypto.KeyWrapper; import org.keycloak.jose.jwk.JSONWebKeySet; import org.keycloak.jose.jwk.JWK; import org.keycloak.keys.PublicKeyLoader; @@ -56,21 +60,18 @@ public class ClientPublicKeyLoader implements PublicKeyLoader { @Override - public Map loadKeys() throws Exception { + public Map loadKeys() throws Exception { OIDCAdvancedConfigWrapper config = OIDCAdvancedConfigWrapper.fromClientModel(client); if (config.isUseJwksUrl()) { String jwksUrl = config.getJwksUrl(); jwksUrl = ResolveRelative.resolveRelativeUri(session.getContext().getUri().getRequestUri(), client.getRootUrl(), jwksUrl); JSONWebKeySet jwks = JWKSHttpUtils.sendJwksRequest(session, jwksUrl); - return JWKSUtils.getKeysForUse(jwks, JWK.Use.SIG); + return JWKSUtils.getKeyWrappersForUse(jwks, JWK.Use.SIG); } else { try { CertificateRepresentation certInfo = CertificateInfoHelper.getCertificateFromClient(client, JWTClientAuthenticator.ATTR_PREFIX); - PublicKey publicKey = getSignatureValidationKey(certInfo); - - // Check if we have kid in DB, generate otherwise - String kid = certInfo.getKid() != null ? certInfo.getKid() : KeyUtils.createKeyId(publicKey); - return Collections.singletonMap(kid, publicKey); + KeyWrapper publicKey = getSignatureValidationKey(certInfo); + return Collections.singletonMap(publicKey.getKid(), publicKey); } catch (ModelException me) { logger.warnf(me, "Unable to retrieve publicKey for verify signature of client '%s' . Error details: %s", client.getClientId(), me.getMessage()); return Collections.emptyMap(); @@ -79,7 +80,8 @@ public class ClientPublicKeyLoader implements PublicKeyLoader { } } - private static PublicKey getSignatureValidationKey(CertificateRepresentation certInfo) throws ModelException { + private static KeyWrapper getSignatureValidationKey(CertificateRepresentation certInfo) throws ModelException { + KeyWrapper keyWrapper = new KeyWrapper(); String encodedCertificate = certInfo.getCertificate(); String encodedPublicKey = certInfo.getPublicKey(); @@ -91,12 +93,25 @@ public class ClientPublicKeyLoader implements PublicKeyLoader { throw new ModelException("Client has both publicKey and certificate configured"); } + keyWrapper.setAlgorithm(Algorithm.RS256); + keyWrapper.setType(KeyType.RSA); + keyWrapper.setUse(KeyUse.SIG); + String kid = null; if (encodedCertificate != null) { X509Certificate clientCert = KeycloakModelUtils.getCertificate(encodedCertificate); - return clientCert.getPublicKey(); + // Check if we have kid in DB, generate otherwise + kid = certInfo.getKid() != null ? certInfo.getKid() : KeyUtils.createKeyId(clientCert.getPublicKey()); + keyWrapper.setKid(kid); + keyWrapper.setVerifyKey(clientCert.getPublicKey()); + keyWrapper.setCertificate(clientCert); } else { - return KeycloakModelUtils.getPublicKey(encodedPublicKey); + PublicKey publicKey = KeycloakModelUtils.getPublicKey(encodedPublicKey); + // Check if we have kid in DB, generate otherwise + kid = certInfo.getKid() != null ? certInfo.getKid() : KeyUtils.createKeyId(publicKey); + keyWrapper.setKid(kid); + keyWrapper.setVerifyKey(publicKey); } + return keyWrapper; } diff --git a/services/src/main/java/org/keycloak/keys/loader/HardcodedPublicKeyLoader.java b/services/src/main/java/org/keycloak/keys/loader/HardcodedPublicKeyLoader.java index 213694b5c80..846559ab168 100644 --- a/services/src/main/java/org/keycloak/keys/loader/HardcodedPublicKeyLoader.java +++ b/services/src/main/java/org/keycloak/keys/loader/HardcodedPublicKeyLoader.java @@ -17,9 +17,12 @@ package org.keycloak.keys.loader; import org.keycloak.common.util.PemUtils; +import org.keycloak.crypto.Algorithm; +import org.keycloak.crypto.KeyType; +import org.keycloak.crypto.KeyUse; +import org.keycloak.crypto.KeyWrapper; import org.keycloak.keys.PublicKeyLoader; -import java.security.PublicKey; import java.util.Collections; import java.util.Map; @@ -38,15 +41,20 @@ public class HardcodedPublicKeyLoader implements PublicKeyLoader { } @Override - public Map loadKeys() throws Exception { + public Map loadKeys() throws Exception { return Collections.unmodifiableMap(Collections.singletonMap(kid, getSavedPublicKey())); } - protected PublicKey getSavedPublicKey() { + protected KeyWrapper getSavedPublicKey() { + KeyWrapper keyWrapper = null; if (pem != null && ! pem.trim().equals("")) { - return PemUtils.decodePublicKey(pem); - } else { - return null; + keyWrapper = new KeyWrapper(); + keyWrapper.setKid(kid); + keyWrapper.setType(KeyType.RSA); + keyWrapper.setAlgorithm(Algorithm.RS256); + keyWrapper.setUse(KeyUse.SIG); + keyWrapper.setVerifyKey(PemUtils.decodePublicKey(pem)); } + return keyWrapper; } } diff --git a/services/src/main/java/org/keycloak/keys/loader/OIDCIdentityProviderPublicKeyLoader.java b/services/src/main/java/org/keycloak/keys/loader/OIDCIdentityProviderPublicKeyLoader.java index fda2c2f4007..7bc0ec0745d 100644 --- a/services/src/main/java/org/keycloak/keys/loader/OIDCIdentityProviderPublicKeyLoader.java +++ b/services/src/main/java/org/keycloak/keys/loader/OIDCIdentityProviderPublicKeyLoader.java @@ -21,6 +21,10 @@ import org.jboss.logging.Logger; import org.keycloak.broker.oidc.OIDCIdentityProviderConfig; import org.keycloak.common.util.KeyUtils; import org.keycloak.common.util.PemUtils; +import org.keycloak.crypto.Algorithm; +import org.keycloak.crypto.KeyType; +import org.keycloak.crypto.KeyUse; +import org.keycloak.crypto.KeyWrapper; import org.keycloak.jose.jwk.JSONWebKeySet; import org.keycloak.jose.jwk.JWK; import org.keycloak.keys.PublicKeyLoader; @@ -48,23 +52,18 @@ public class OIDCIdentityProviderPublicKeyLoader implements PublicKeyLoader { } @Override - public Map loadKeys() throws Exception { + public Map loadKeys() throws Exception { if (config.isUseJwksUrl()) { String jwksUrl = config.getJwksUrl(); JSONWebKeySet jwks = JWKSHttpUtils.sendJwksRequest(session, jwksUrl); - return JWKSUtils.getKeysForUse(jwks, JWK.Use.SIG); + return JWKSUtils.getKeyWrappersForUse(jwks, JWK.Use.SIG); } else { try { - PublicKey publicKey = getSavedPublicKey(); + KeyWrapper publicKey = getSavedPublicKey(); if (publicKey == null) { return Collections.emptyMap(); } - - String presetKeyId = config.getPublicKeySignatureVerifierKeyId(); - String kid = (presetKeyId == null || presetKeyId.trim().isEmpty()) - ? KeyUtils.createKeyId(publicKey) - : presetKeyId; - return Collections.singletonMap(kid, publicKey); + return Collections.singletonMap(publicKey.getKid(), publicKey); } catch (Exception e) { logger.warnf(e, "Unable to retrieve publicKey for verify signature of identityProvider '%s' . Error details: %s", config.getAlias(), e.getMessage()); return Collections.emptyMap(); @@ -72,12 +71,23 @@ public class OIDCIdentityProviderPublicKeyLoader implements PublicKeyLoader { } } - protected PublicKey getSavedPublicKey() throws Exception { + protected KeyWrapper getSavedPublicKey() throws Exception { + KeyWrapper keyWrapper = null; if (config.getPublicKeySignatureVerifier() != null && !config.getPublicKeySignatureVerifier().trim().equals("")) { - return PemUtils.decodePublicKey(config.getPublicKeySignatureVerifier()); + PublicKey publicKey = PemUtils.decodePublicKey(config.getPublicKeySignatureVerifier()); + keyWrapper = new KeyWrapper(); + String presetKeyId = config.getPublicKeySignatureVerifierKeyId(); + String kid = (presetKeyId == null || presetKeyId.trim().isEmpty()) + ? KeyUtils.createKeyId(publicKey) + : presetKeyId; + keyWrapper.setKid(kid); + keyWrapper.setType(KeyType.RSA); + keyWrapper.setAlgorithm(Algorithm.RS256); + keyWrapper.setUse(KeyUse.SIG); + keyWrapper.setVerifyKey(publicKey); } else { logger.warnf("No public key saved on identityProvider %s", config.getAlias()); - return null; } + return keyWrapper; } } diff --git a/services/src/main/java/org/keycloak/keys/loader/PublicKeyStorageManager.java b/services/src/main/java/org/keycloak/keys/loader/PublicKeyStorageManager.java index 0f28ff85778..b9e522257a9 100644 --- a/services/src/main/java/org/keycloak/keys/loader/PublicKeyStorageManager.java +++ b/services/src/main/java/org/keycloak/keys/loader/PublicKeyStorageManager.java @@ -19,6 +19,7 @@ package org.keycloak.keys.loader; import org.jboss.logging.Logger; import org.keycloak.broker.oidc.OIDCIdentityProviderConfig; +import org.keycloak.crypto.KeyWrapper; import org.keycloak.jose.jws.JWSInput; import org.keycloak.keys.PublicKeyLoader; import org.keycloak.keys.PublicKeyStorageProvider; @@ -37,16 +38,22 @@ public class PublicKeyStorageManager { private static final Logger logger = Logger.getLogger(PublicKeyStorageManager.class); public static PublicKey getClientPublicKey(KeycloakSession session, ClientModel client, JWSInput input) { + KeyWrapper keyWrapper = getClientPublicKeyWrapper(session, client, input); + PublicKey publicKey = null; + if (keyWrapper != null) { + publicKey = (PublicKey)keyWrapper.getVerifyKey(); + } + return publicKey; + } + + public static KeyWrapper getClientPublicKeyWrapper(KeycloakSession session, ClientModel client, JWSInput input) { String kid = input.getHeader().getKeyId(); - PublicKeyStorageProvider keyStorage = session.getProvider(PublicKeyStorageProvider.class); - String modelKey = PublicKeyStorageUtils.getClientModelCacheKey(client.getRealm().getId(), client.getId()); ClientPublicKeyLoader loader = new ClientPublicKeyLoader(session, client); return keyStorage.getPublicKey(modelKey, kid, loader); } - public static PublicKey getIdentityProviderPublicKey(KeycloakSession session, RealmModel realm, OIDCIdentityProviderConfig idpConfig, JWSInput input) { boolean keyIdSetInConfiguration = idpConfig.getPublicKeySignatureVerifierKeyId() != null && ! idpConfig.getPublicKeySignatureVerifierKeyId().trim().isEmpty(); @@ -73,6 +80,6 @@ public class PublicKeyStorageManager { : kid, pem); } - return keyStorage.getPublicKey(modelKey, kid, loader); + return (PublicKey)keyStorage.getPublicKey(modelKey, kid, loader).getVerifyKey(); } } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java index ec68cbdc395..db8bc5486bf 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java @@ -20,6 +20,7 @@ package org.keycloak.protocol.oidc; import org.keycloak.OAuth2Constants; import org.keycloak.authentication.ClientAuthenticator; import org.keycloak.authentication.ClientAuthenticatorFactory; +import org.keycloak.crypto.ClientSignatureVerifierProvider; import org.keycloak.crypto.SignatureProvider; import org.keycloak.jose.jws.Algorithm; import org.keycloak.models.ClientScopeModel; @@ -46,8 +47,6 @@ import java.util.List; */ public class OIDCWellKnownProvider implements WellKnownProvider { - public static final List DEFAULT_REQUEST_OBJECT_SIGNING_ALG_VALUES_SUPPORTED = list(Algorithm.none.toString(), Algorithm.RS256.toString()); - public static final List DEFAULT_GRANT_TYPES_SUPPORTED = list(OAuth2Constants.AUTHORIZATION_CODE, OAuth2Constants.IMPLICIT, OAuth2Constants.REFRESH_TOKEN, OAuth2Constants.PASSWORD, OAuth2Constants.CLIENT_CREDENTIALS); public static final List DEFAULT_RESPONSE_TYPES_SUPPORTED = list(OAuth2Constants.CODE, OIDCResponseType.NONE, OIDCResponseType.ID_TOKEN, OIDCResponseType.TOKEN, "id_token token", "code id_token", "code token", "code id_token token"); @@ -92,7 +91,7 @@ public class OIDCWellKnownProvider implements WellKnownProvider { config.setIdTokenSigningAlgValuesSupported(getSupportedSigningAlgorithms(false)); config.setUserInfoSigningAlgValuesSupported(getSupportedSigningAlgorithms(true)); - config.setRequestObjectSigningAlgValuesSupported(DEFAULT_REQUEST_OBJECT_SIGNING_ALG_VALUES_SUPPORTED); + config.setRequestObjectSigningAlgValuesSupported(getSupportedClientSigningAlgorithms(true)); config.setResponseTypesSupported(DEFAULT_RESPONSE_TYPES_SUPPORTED); config.setSubjectTypesSupported(DEFAULT_SUBJECT_TYPES_SUPPORTED); config.setResponseModesSupported(DEFAULT_RESPONSE_MODES_SUPPORTED); @@ -163,4 +162,14 @@ public class OIDCWellKnownProvider implements WellKnownProvider { return result; } + private List getSupportedClientSigningAlgorithms(boolean includeNone) { + List result = new LinkedList<>(); + for (ProviderFactory s : session.getKeycloakSessionFactory().getProviderFactories(ClientSignatureVerifierProvider.class)) { + result.add(s.getId()); + } + if (includeNone) { + result.add("none"); + } + return result; + } } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestObjectParser.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestObjectParser.java index 6803680c948..42544db5f83 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestObjectParser.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestObjectParser.java @@ -17,16 +17,15 @@ package org.keycloak.protocol.oidc.endpoints.request; import com.fasterxml.jackson.databind.JsonNode; -import java.security.PublicKey; import java.util.HashMap; import java.util.HashSet; import java.util.Set; +import org.keycloak.crypto.SignatureProvider; +import org.keycloak.crypto.SignatureVerifierContext; import org.keycloak.jose.jws.Algorithm; import org.keycloak.jose.jws.JWSHeader; import org.keycloak.jose.jws.JWSInput; -import org.keycloak.jose.jws.crypto.RSAProvider; -import org.keycloak.keys.loader.PublicKeyStorageManager; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; @@ -44,29 +43,24 @@ class AuthzEndpointRequestObjectParser extends AuthzEndpointRequestParser { public AuthzEndpointRequestObjectParser(KeycloakSession session, String requestObject, ClientModel client) throws Exception { JWSInput input = new JWSInput(requestObject); JWSHeader header = input.getHeader(); + Algorithm headerAlgorithm = header.getAlgorithm(); Algorithm requestedSignatureAlgorithm = OIDCAdvancedConfigWrapper.fromClientModel(client).getRequestObjectSignatureAlg(); - if (requestedSignatureAlgorithm != null && requestedSignatureAlgorithm != header.getAlgorithm()) { + if (headerAlgorithm == null) { + throw new RuntimeException("Request object signed algorithm not specified"); + } + if (requestedSignatureAlgorithm != null && requestedSignatureAlgorithm != headerAlgorithm) { throw new RuntimeException("Request object signed with different algorithm than client requested algorithm"); } if (header.getAlgorithm() == Algorithm.none) { - this.requestParams = JsonSerialization.readValue(input.getContent(), JsonNode.class); - } else if (header.getAlgorithm() == Algorithm.RS256) { - PublicKey clientPublicKey = PublicKeyStorageManager.getClientPublicKey(session, client, input); - if (clientPublicKey == null) { - throw new RuntimeException("Client public key not found"); - } - - boolean verified = RSAProvider.verify(input, clientPublicKey); - if (!verified) { - throw new RuntimeException("Failed to verify signature on 'request' object"); - } - this.requestParams = JsonSerialization.readValue(input.getContent(), JsonNode.class); } else { - throw new RuntimeException("Unsupported JWA algorithm used for signed request"); + this.requestParams = session.tokens().decodeClientJWT(requestObject, client, JsonNode.class); + if (this.requestParams == null) { + throw new RuntimeException("Failed to verify signature on 'request' object"); + } } } diff --git a/services/src/main/resources/META-INF/services/org.keycloak.crypto.ClientSignatureVerifierProviderFactory b/services/src/main/resources/META-INF/services/org.keycloak.crypto.ClientSignatureVerifierProviderFactory new file mode 100644 index 00000000000..594281d3873 --- /dev/null +++ b/services/src/main/resources/META-INF/services/org.keycloak.crypto.ClientSignatureVerifierProviderFactory @@ -0,0 +1,6 @@ +org.keycloak.crypto.RS256ClientSignatureVerifierProviderFactory +org.keycloak.crypto.RS384ClientSignatureVerifierProviderFactory +org.keycloak.crypto.RS512ClientSignatureVerifierProviderFactory +org.keycloak.crypto.ES256ClientSignatureVerifierProviderFactory +org.keycloak.crypto.ES384ClientSignatureVerifierProviderFactory +org.keycloak.crypto.ES512ClientSignatureVerifierProviderFactory \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProviderFactory.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProviderFactory.java index d8d2a8d1636..82246239791 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProviderFactory.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProviderFactory.java @@ -18,6 +18,8 @@ package org.keycloak.testsuite.rest; import org.keycloak.Config.Scope; +import org.keycloak.crypto.Algorithm; +import org.keycloak.crypto.KeyType; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.representations.adapters.action.LogoutAction; @@ -70,6 +72,8 @@ public class TestApplicationResourceProviderFactory implements RealmResourceProv private KeyPair signingKeyPair; private String oidcRequest; private List sectorIdentifierRedirectUris; + private String signingKeyType = KeyType.RSA; + private String signingKeyAlgorithm = Algorithm.RS256; public KeyPair getSigningKeyPair() { return signingKeyPair; @@ -94,5 +98,21 @@ public class TestApplicationResourceProviderFactory implements RealmResourceProv public void setSectorIdentifierRedirectUris(List sectorIdentifierRedirectUris) { this.sectorIdentifierRedirectUris = sectorIdentifierRedirectUris; } + + public String getSigningKeyType() { + return signingKeyType; + } + + public void setSigningKeyType(String signingKeyType) { + this.signingKeyType = signingKeyType; + } + + public String getSigningKeyAlgorithm() { + return signingKeyAlgorithm; + } + + public void setSigningKeyAlgorithm(String signingKeyAlgorithm) { + this.signingKeyAlgorithm = signingKeyAlgorithm; + } } } diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingOIDCEndpointsApplicationResource.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingOIDCEndpointsApplicationResource.java index fd35cafe468..3f5665e996a 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingOIDCEndpointsApplicationResource.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingOIDCEndpointsApplicationResource.java @@ -22,6 +22,10 @@ import org.jboss.resteasy.spi.BadRequestException; import org.keycloak.OAuth2Constants; import org.keycloak.common.util.KeyUtils; import org.keycloak.common.util.PemUtils; +import org.keycloak.crypto.AsymmetricSignatureSignerContext; +import org.keycloak.crypto.KeyType; +import org.keycloak.crypto.KeyWrapper; +import org.keycloak.crypto.SignatureSignerContext; import org.keycloak.jose.jwk.JSONWebKeySet; import org.keycloak.jose.jwk.JWK; import org.keycloak.jose.jwk.JWKBuilder; @@ -36,10 +40,13 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; +import java.security.InvalidAlgorithmParameterException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; +import java.security.SecureRandom; +import java.security.spec.ECGenParameterSpec; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -63,17 +70,52 @@ public class TestingOIDCEndpointsApplicationResource { @Produces(MediaType.APPLICATION_JSON) @Path("/generate-keys") @NoCache - public Map generateKeys() { + public Map generateKeys(@QueryParam("jwaAlgorithm") String jwaAlgorithm) { try { - KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048); + KeyPair keyPair = null; + if (jwaAlgorithm == null) jwaAlgorithm = org.keycloak.crypto.Algorithm.RS256; + String keyType = null; + + switch (jwaAlgorithm) { + case org.keycloak.crypto.Algorithm.RS256: + case org.keycloak.crypto.Algorithm.RS384: + case org.keycloak.crypto.Algorithm.RS512: + keyType = KeyType.RSA; + keyPair = KeyUtils.generateRsaKeyPair(2048); + break; + case org.keycloak.crypto.Algorithm.ES256: + keyType = KeyType.EC; + keyPair = generateEcdsaKey("secp256r1"); + break; + case org.keycloak.crypto.Algorithm.ES384: + keyType = KeyType.EC; + keyPair = generateEcdsaKey("secp384r1"); + break; + case org.keycloak.crypto.Algorithm.ES512: + keyType = KeyType.EC; + keyPair = generateEcdsaKey("secp521r1"); + break; + default : + throw new RuntimeException("Unsupported signature algorithm"); + } + clientData.setSigningKeyPair(keyPair); + clientData.setSigningKeyType(keyType); + clientData.setSigningKeyAlgorithm(jwaAlgorithm); } catch (Exception e) { throw new BadRequestException("Error generating signing keypair", e); } - return getKeysAsPem(); } + private KeyPair generateEcdsaKey(String ecDomainParamName) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC"); + SecureRandom randomGen = SecureRandom.getInstance("SHA1PRNG"); + ECGenParameterSpec ecSpec = new ECGenParameterSpec(ecDomainParamName); + keyGen.initialize(ecSpec, randomGen); + KeyPair keyPair = keyGen.generateKeyPair(); + return keyPair; + } @GET @Produces(MediaType.APPLICATION_JSON) @@ -95,11 +137,18 @@ public class TestingOIDCEndpointsApplicationResource { @NoCache public JSONWebKeySet getJwks() { JSONWebKeySet keySet = new JSONWebKeySet(); + KeyPair signingKeyPair = clientData.getSigningKeyPair(); + String signingKeyAlgorithm = clientData.getSigningKeyAlgorithm(); + String signingKeyType = clientData.getSigningKeyType(); - if (clientData.getSigningKeyPair() == null) { + if (signingKeyPair == null || !isSupportedSigningAlgorithm(signingKeyAlgorithm)) { keySet.setKeys(new JWK[] {}); + } else if (KeyType.RSA.equals(signingKeyType)) { + keySet.setKeys(new JWK[] { JWKBuilder.create().algorithm(signingKeyAlgorithm).rsa(signingKeyPair.getPublic()) }); + } else if (KeyType.EC.equals(signingKeyType)) { + keySet.setKeys(new JWK[] { JWKBuilder.create().algorithm(signingKeyAlgorithm).ec(signingKeyPair.getPublic()) }); } else { - keySet.setKeys(new JWK[] { JWKBuilder.create().rs256(clientData.getSigningKeyPair().getPublic()) }); + keySet.setKeys(new JWK[] {}); } return keySet; @@ -113,6 +162,7 @@ public class TestingOIDCEndpointsApplicationResource { public void setOIDCRequest(@QueryParam("realmName") String realmName, @QueryParam("clientId") String clientId, @QueryParam("redirectUri") String redirectUri, @QueryParam("maxAge") String maxAge, @QueryParam("jwaAlgorithm") String jwaAlgorithm) { + Map oidcRequest = new HashMap<>(); oidcRequest.put(OIDCLoginProtocol.CLIENT_ID_PARAM, clientId); oidcRequest.put(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, OAuth2Constants.CODE); @@ -121,21 +171,38 @@ public class TestingOIDCEndpointsApplicationResource { oidcRequest.put(OIDCLoginProtocol.MAX_AGE_PARAM, Integer.parseInt(maxAge)); } - Algorithm alg = Enum.valueOf(Algorithm.class, jwaAlgorithm); - if (alg == Algorithm.none) { - clientData.setOidcRequest(new JWSBuilder().jsonContent(oidcRequest).none()); - } else if (alg == Algorithm.RS256) { - if (clientData.getSigningKeyPair() == null) { - throw new BadRequestException("Requested RS256, but signing key not set"); - } + if (!isSupportedSigningAlgorithm(jwaAlgorithm)) throw new BadRequestException("Unknown argument: " + jwaAlgorithm); + if ("none".equals(jwaAlgorithm)) { + clientData.setOidcRequest(new JWSBuilder().jsonContent(oidcRequest).none()); + } else if (clientData.getSigningKeyPair() == null) { + throw new BadRequestException("signing key not set"); + } else { PrivateKey privateKey = clientData.getSigningKeyPair().getPrivate(); String kid = KeyUtils.createKeyId(clientData.getSigningKeyPair().getPublic()); - clientData.setOidcRequest(new JWSBuilder().kid(kid).jsonContent(oidcRequest).rsa256(privateKey)); - } else { - throw new BadRequestException("Unknown argument: " + jwaAlgorithm); + KeyWrapper keyWrapper = new KeyWrapper(); + keyWrapper.setAlgorithm(clientData.getSigningKeyAlgorithm()); + keyWrapper.setKid(kid); + keyWrapper.setSignKey(privateKey); + SignatureSignerContext signer = new AsymmetricSignatureSignerContext(keyWrapper); + clientData.setOidcRequest(new JWSBuilder().kid(kid).jsonContent(oidcRequest).sign(signer)); } } + + private boolean isSupportedSigningAlgorithm(String signingAlgorithm) { + boolean ret = false; + switch (signingAlgorithm) { + case "none": + case org.keycloak.crypto.Algorithm.RS256: + case org.keycloak.crypto.Algorithm.RS384: + case org.keycloak.crypto.Algorithm.RS512: + case org.keycloak.crypto.Algorithm.ES256: + case org.keycloak.crypto.Algorithm.ES384: + case org.keycloak.crypto.Algorithm.ES512: + ret = true; + } + return ret; + } @GET diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestOIDCEndpointsApplicationResource.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestOIDCEndpointsApplicationResource.java index f8d8e9828fb..19fd5d4e8fd 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestOIDCEndpointsApplicationResource.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestOIDCEndpointsApplicationResource.java @@ -35,7 +35,7 @@ public interface TestOIDCEndpointsApplicationResource { @GET @Produces(MediaType.APPLICATION_JSON) @Path("/generate-keys") - Map generateKeys(); + Map generateKeys(@QueryParam("jwaAlgorithm") String jwaAlgorithm); @GET @Produces(MediaType.APPLICATION_JSON) diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java index ff3e520982a..a86d22d2257 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java @@ -207,17 +207,30 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest { @Test public void testSignaturesRequired() throws Exception { OIDCClientRepresentation clientRep = createRep(); - clientRep.setUserinfoSignedResponseAlg(Algorithm.RS256.toString()); - clientRep.setRequestObjectSigningAlg(Algorithm.RS256.toString()); + clientRep.setUserinfoSignedResponseAlg(Algorithm.ES256.toString()); + clientRep.setRequestObjectSigningAlg(Algorithm.ES256.toString()); OIDCClientRepresentation response = reg.oidc().create(clientRep); - Assert.assertEquals(Algorithm.RS256.toString(), response.getUserinfoSignedResponseAlg()); - Assert.assertEquals(Algorithm.RS256.toString(), response.getRequestObjectSigningAlg()); + Assert.assertEquals(Algorithm.ES256.toString(), response.getUserinfoSignedResponseAlg()); + Assert.assertEquals(Algorithm.ES256.toString(), response.getRequestObjectSigningAlg()); Assert.assertNotNull(response.getClientSecret()); // Test Keycloak representation ClientRepresentation kcClient = getClient(response.getClientId()); OIDCAdvancedConfigWrapper config = OIDCAdvancedConfigWrapper.fromClientRepresentation(kcClient); + Assert.assertEquals(config.getUserInfoSignedResponseAlg(), Algorithm.ES256); + Assert.assertEquals(config.getRequestObjectSignatureAlg(), Algorithm.ES256); + + // update (ES256 to RS256) + clientRep.setUserinfoSignedResponseAlg(Algorithm.RS256.toString()); + clientRep.setRequestObjectSigningAlg(Algorithm.RS256.toString()); + response = reg.oidc().create(clientRep); + Assert.assertEquals(Algorithm.RS256.toString(), response.getUserinfoSignedResponseAlg()); + Assert.assertEquals(Algorithm.RS256.toString(), response.getRequestObjectSigningAlg()); + + // keycloak representation + kcClient = getClient(response.getClientId()); + config = OIDCAdvancedConfigWrapper.fromClientRepresentation(kcClient); Assert.assertEquals(config.getUserInfoSignedResponseAlg(), Algorithm.RS256); Assert.assertEquals(config.getRequestObjectSignatureAlg(), Algorithm.RS256); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCJwksClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCJwksClientRegistrationTest.java index fe2e8864113..0c8dd0a0db3 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCJwksClientRegistrationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCJwksClientRegistrationTest.java @@ -28,7 +28,6 @@ import java.util.Map; import javax.ws.rs.core.UriBuilder; -import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.CloseableHttpResponse; @@ -49,7 +48,6 @@ import org.keycloak.jose.jwk.JWK; import org.keycloak.jose.jwk.JWKBuilder; import org.keycloak.jose.jws.JWSBuilder; import org.keycloak.keys.PublicKeyStorageUtils; -import org.keycloak.keys.loader.PublicKeyStorageManager; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocolService; @@ -106,7 +104,7 @@ public class OIDCJwksClientRegistrationTest extends AbstractClientRegistrationTe // Generate keys for client TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints(); - Map generatedKeys = oidcClientEndpointsResource.generateKeys(); + Map generatedKeys = oidcClientEndpointsResource.generateKeys("RS256"); JSONWebKeySet keySet = oidcClientEndpointsResource.getJwks(); clientRep.setJwks(keySet); @@ -131,7 +129,7 @@ public class OIDCJwksClientRegistrationTest extends AbstractClientRegistrationTe // Generate keys for client TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints(); - Map generatedKeys = oidcClientEndpointsResource.generateKeys(); + Map generatedKeys = oidcClientEndpointsResource.generateKeys("RS256"); JSONWebKeySet keySet = oidcClientEndpointsResource.getJwks(); clientRep.setJwks(keySet); @@ -163,7 +161,7 @@ public class OIDCJwksClientRegistrationTest extends AbstractClientRegistrationTe // Generate keys for client TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints(); - oidcClientEndpointsResource.generateKeys(); + oidcClientEndpointsResource.generateKeys("RS256"); JSONWebKeySet keySet = oidcClientEndpointsResource.getJwks(); @@ -250,7 +248,7 @@ public class OIDCJwksClientRegistrationTest extends AbstractClientRegistrationTe // Generate keys for client TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints(); - Map generatedKeys = oidcClientEndpointsResource.generateKeys(); + Map generatedKeys = oidcClientEndpointsResource.generateKeys("RS256"); clientRep.setJwksUri(TestApplicationResourceUrls.clientJwksUri()); @@ -273,7 +271,7 @@ public class OIDCJwksClientRegistrationTest extends AbstractClientRegistrationTe // Generate keys for client TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints(); - Map generatedKeys = oidcClientEndpointsResource.generateKeys(); + Map generatedKeys = oidcClientEndpointsResource.generateKeys("RS256"); clientRep.setJwksUri(TestApplicationResourceUrls.clientJwksUri()); @@ -287,7 +285,7 @@ public class OIDCJwksClientRegistrationTest extends AbstractClientRegistrationTe assertAuthenticateClientSuccess(generatedKeys, response, KEEP_GENERATED_KID); // Add new key to the jwks - Map generatedKeys2 = oidcClientEndpointsResource.generateKeys(); + Map generatedKeys2 = oidcClientEndpointsResource.generateKeys("RS256"); // Error should happen. KeyStorageProvider won't yet download new keys because of timeout assertAuthenticateClientError(generatedKeys2, response, KEEP_GENERATED_KID); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java index 80fbe540fce..01859b03f17 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java @@ -796,7 +796,7 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest assertEquals("Invalid Request", errorPage.getError()); // Generate keypair for client - String clientPublicKeyPem = oidcClientEndpointsResource.generateKeys().get(TestingOIDCEndpointsApplicationResource.PUBLIC_KEY); + String clientPublicKeyPem = oidcClientEndpointsResource.generateKeys(null).get(TestingOIDCEndpointsApplicationResource.PUBLIC_KEY); // Verify signed request_uri will fail due to failed signature validation oidcClientEndpointsResource.setOIDCRequest("test", "test-app", validRedirectUri, "10", Algorithm.RS256.toString()); @@ -826,6 +826,117 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest clientResource.update(clientRep); } + private void requestUriParamSignedIn(Algorithm expectedAlgorithm, Algorithm actualAlgorithm) throws Exception { + ClientResource clientResource = null; + ClientRepresentation clientRep = null; + try { + oauth.stateParamHardcoded("mystate3"); + + String validRedirectUri = oauth.getRedirectUri(); + TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints(); + + // Set required signature for request_uri + clientResource = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"); + clientRep = clientResource.toRepresentation(); + OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setRequestObjectSignatureAlg(expectedAlgorithm); + clientResource.update(clientRep); + + // generate and register client keypair + if (Algorithm.none != actualAlgorithm) oidcClientEndpointsResource.generateKeys(actualAlgorithm.name()); + + // register request object + oidcClientEndpointsResource.setOIDCRequest("test", "test-app", validRedirectUri, "10", actualAlgorithm.name()); + + // use and set jwks_url + clientResource = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"); + clientRep = clientResource.toRepresentation(); + OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setUseJwksUrl(true); + String jwksUrl = TestApplicationResourceUrls.clientJwksUri(); + OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setJwksUrl(jwksUrl); + clientResource.update(clientRep); + + // set time offset, so that new keys are downloaded + setTimeOffset(20); + + oauth.requestUri(TestApplicationResourceUrls.clientRequestUri()); + if (expectedAlgorithm == null || expectedAlgorithm == actualAlgorithm) { + // Check signed request_uri will pass + OAuthClient.AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password"); + Assert.assertNotNull(response.getCode()); + Assert.assertEquals("mystate3", response.getState()); + assertTrue(appPage.isCurrent()); + } else { + // Verify signed request_uri will fail due to failed signature validation + oauth.openLoginForm(); + Assert.assertTrue(errorPage.isCurrent()); + assertEquals("Invalid Request", errorPage.getError()); + } + + } finally { + // Revert requiring signature for client + OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setRequestObjectSignatureAlg(null); + // Revert jwks_url settings + OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setUseJwksUrl(false); + OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setJwksUrl(null); + clientResource.update(clientRep); + } + } + + @Test + public void requestUriParamSignedExpectedES256ActualRS256() throws Exception { + // will fail + requestUriParamSignedIn(Algorithm.ES256, Algorithm.RS256); + } + + @Test + public void requestUriParamSignedExpectedNoneActualES256() throws Exception { + // will fail + requestUriParamSignedIn(Algorithm.none, Algorithm.ES256); + } + + @Test + public void requestUriParamSignedExpectedNoneActualNone() throws Exception { + // will success + requestUriParamSignedIn(Algorithm.none, Algorithm.none); + } + + @Test + public void requestUriParamSignedExpectedES256ActualES256() throws Exception { + // will success + requestUriParamSignedIn(Algorithm.ES256, Algorithm.ES256); + } + + @Test + public void requestUriParamSignedExpectedES384ActualES384() throws Exception { + // will success + requestUriParamSignedIn(Algorithm.ES384, Algorithm.ES384); + } + + @Test + public void requestUriParamSignedExpectedES512ActualES512() throws Exception { + // will success + requestUriParamSignedIn(Algorithm.ES512, Algorithm.ES512); + } + + @Test + public void requestUriParamSignedExpectedRS384ActualRS384() throws Exception { + // will success + requestUriParamSignedIn(Algorithm.RS384, Algorithm.RS384); + } + + @Test + public void requestUriParamSignedExpectedRS512ActualRS512() throws Exception { + // will success + requestUriParamSignedIn(Algorithm.RS512, Algorithm.RS512); + } + + @Test + public void requestUriParamSignedExpectedAnyActualES256() throws Exception { + // Algorithm is null if 'any' + // will success + requestUriParamSignedIn(null, Algorithm.ES256); + } + // LOGIN_HINT @Test diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java index 26a740e5289..2da4622abc2 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java @@ -97,7 +97,6 @@ public class OIDCWellKnownProviderTest extends AbstractKeycloakTest { oauth.clientId("test-app"); } - @Test public void testDiscovery() { Client client = ClientBuilder.newClient(); @@ -127,7 +126,7 @@ public class OIDCWellKnownProviderTest extends AbstractKeycloakTest { Assert.assertNames(oidcConfig.getSubjectTypesSupported(), "pairwise", "public"); Assert.assertNames(oidcConfig.getIdTokenSigningAlgValuesSupported(), Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512); Assert.assertNames(oidcConfig.getUserInfoSigningAlgValuesSupported(), "none", Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512); - Assert.assertNames(oidcConfig.getRequestObjectSigningAlgValuesSupported(), "none", Algorithm.RS256); + Assert.assertNames(oidcConfig.getRequestObjectSigningAlgValuesSupported(), "none", Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512); // Client authentication Assert.assertNames(oidcConfig.getTokenEndpointAuthMethodsSupported(), "client_secret_basic", "client_secret_post", "private_key_jwt", "client_secret_jwt"); diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js index cabe47ab093..e86371b1bf8 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js @@ -959,12 +959,6 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, flows, $ro {name: "INCLUSIVE_WITH_COMMENTS", value: "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"} ]; - $scope.requestObjectSignatureAlgorithms = [ - "any", - "none", - "RS256" - ]; - $scope.requestObjectRequiredOptions = [ "not required", "request or request_uri", diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html index 38846719a6c..ca540bec8ce 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html @@ -438,8 +438,10 @@