keycloak-uncached
Changes
model/infinispan/src/main/java/org/keycloak/keys/infinispan/InfinispanPublicKeyStorageProvider.java 12(+6 -6)
model/infinispan/src/test/java/org/keycloak/keys/infinispan/InfinispanKeyStorageProviderTest.java 4(+2 -2)
server-spi-private/src/main/java/org/keycloak/crypto/ClientSignatureVerifierProvider.java 31(+31 -0)
server-spi-private/src/main/java/org/keycloak/crypto/ClientSignatureVerifierProviderFactory.java 38(+38 -0)
services/src/main/java/org/keycloak/crypto/AsymmetricClientSignatureVerifierProvider.java 38(+38 -0)
services/src/main/java/org/keycloak/crypto/ES256ClientSignatureVerifierProviderFactory.java 35(+35 -0)
services/src/main/java/org/keycloak/crypto/ES384ClientSignatureVerifierProviderFactory.java 35(+35 -0)
services/src/main/java/org/keycloak/crypto/ES512ClientSignatureVerifierProviderFactory.java 35(+35 -0)
services/src/main/java/org/keycloak/crypto/RS256ClientSignatureVerifierProviderFactory.java 35(+35 -0)
services/src/main/java/org/keycloak/crypto/RS384ClientSignatureVerifierProviderFactory.java 35(+35 -0)
services/src/main/java/org/keycloak/crypto/RS512ClientSignatureVerifierProviderFactory.java 35(+35 -0)
services/src/main/java/org/keycloak/keys/loader/OIDCIdentityProviderPublicKeyLoader.java 34(+22 -12)
services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestObjectParser.java 28(+11 -17)
services/src/main/resources/META-INF/services/org.keycloak.crypto.ClientSignatureVerifierProviderFactory 6(+6 -0)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingOIDCEndpointsApplicationResource.java 97(+82 -15)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProviderFactory.java 20(+20 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestOIDCEndpointsApplicationResource.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java 21(+17 -4)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCJwksClientRegistrationTest.java 14(+6 -8)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java 113(+112 -1)
Details
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 19644aa..d97c7e8 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 b27c48c..db59973 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 8db20e8..1745239 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<String, KeyWrapper> getKeyWrappersForUse(JSONWebKeySet keySet, JWK.Use requestedUse) {
+ Map<String, KeyWrapper> 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 933b923..230baec 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<String, PublicKey> publicKeys, String kid) {
+ private KeyWrapper getPublicKey(Map<String, KeyWrapper> 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<String, PublicKey> publicKeys = delegate.loadKeys();
+ Map<String, KeyWrapper> 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 cb0561c..2f2d807 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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@@ -28,9 +29,9 @@ public class PublicKeysEntry implements Serializable {
private final int lastRequestTime;
- private final Map<String, PublicKey> currentKeys;
+ private final Map<String, KeyWrapper> currentKeys;
- public PublicKeysEntry(int lastRequestTime, Map<String, PublicKey> currentKeys) {
+ public PublicKeysEntry(int lastRequestTime, Map<String, KeyWrapper> currentKeys) {
this.lastRequestTime = lastRequestTime;
this.currentKeys = currentKeys;
}
@@ -39,7 +40,7 @@ public class PublicKeysEntry implements Serializable {
return lastRequestTime;
}
- public Map<String, PublicKey> getCurrentKeys() {
+ public Map<String, KeyWrapper> 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 308d7b2..b68d653 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<String, PublicKey> loadKeys() throws Exception {
+ public Map<String, KeyWrapper> loadKeys() throws Exception {
counters.putIfAbsent(modelKey, new AtomicInteger(0));
AtomicInteger currentCounter = counters.get(modelKey);
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 4666e57..bd0244f 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> T decodeClientJWT(String token, ClientModel client, Class<T> clazz);
+
}
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 0000000..a4224cb
--- /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 0000000..d8fd4cd
--- /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<ClientSignatureVerifierProvider> {
+
+ @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 0000000..0461f85
--- /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<? extends Provider> getProviderClass() {
+ return ClientSignatureVerifierProvider.class;
+ }
+
+ @Override
+ public Class<? extends ProviderFactory> 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 c4ec672..3ec53f8 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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface PublicKeyLoader {
- Map<String, PublicKey> loadKeys() throws Exception;
+ Map<String, KeyWrapper> 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 1d72180..437ad23 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 751b8cc..560d15a 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/services/src/main/java/org/keycloak/crypto/AsymmetricClientSignatureVerifierProvider.java b/services/src/main/java/org/keycloak/crypto/AsymmetricClientSignatureVerifierProvider.java
new file mode 100644
index 0000000..fc54945
--- /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 0000000..317c363
--- /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 0000000..c03f040
--- /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 0000000..715daf5
--- /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 0000000..cb76657
--- /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 0000000..5fc1a74
--- /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 0000000..6c1f5c0
--- /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 0000000..efeedb1
--- /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 9a0323d..df9c5e1 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;
@@ -84,6 +85,29 @@ public class DefaultTokenManager implements TokenManager {
}
@Override
+ public <T> T decodeClientJWT(String token, ClientModel client, Class<T> 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) {
case INTERNAL:
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 af2a99c..23f43d7 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<String, PublicKey> loadKeys() throws Exception {
+ public Map<String, KeyWrapper> 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 213694b..846559a 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<String, PublicKey> loadKeys() throws Exception {
+ public Map<String, KeyWrapper> 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 fda2c2f..7bc0ec0 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<String, PublicKey> loadKeys() throws Exception {
+ public Map<String, KeyWrapper> 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 0f28ff8..b9e5222 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) {
- String kid = input.getHeader().getKeyId();
+ 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/endpoints/request/AuthzEndpointRequestObjectParser.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestObjectParser.java
index 6803680..42544db 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/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java
index ec68cbd..db8bc54 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<String> DEFAULT_REQUEST_OBJECT_SIGNING_ALG_VALUES_SUPPORTED = list(Algorithm.none.toString(), Algorithm.RS256.toString());
-
public static final List<String> DEFAULT_GRANT_TYPES_SUPPORTED = list(OAuth2Constants.AUTHORIZATION_CODE, OAuth2Constants.IMPLICIT, OAuth2Constants.REFRESH_TOKEN, OAuth2Constants.PASSWORD, OAuth2Constants.CLIENT_CREDENTIALS);
public static final List<String> 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<String> getSupportedClientSigningAlgorithms(boolean includeNone) {
+ List<String> 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/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 0000000..594281d
--- /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/resource/TestingOIDCEndpointsApplicationResource.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingOIDCEndpointsApplicationResource.java
index fd35caf..3f5665e 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<String, String> generateKeys() {
+ public Map<String, String> 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<String, Object> oidcRequest = new HashMap<>();
oidcRequest.put(OIDCLoginProtocol.CLIENT_ID_PARAM, clientId);
oidcRequest.put(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, OAuth2Constants.CODE);
@@ -121,20 +171,37 @@ 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;
}
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 d8d2a8d..8224623 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<String> 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<String> 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/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 f8d8e98..19fd5d4 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<String, String> generateKeys();
+ Map<String, String> 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 ff3e520..a86d22d 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 fe2e886..0c8dd0a 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<String, String> generatedKeys = oidcClientEndpointsResource.generateKeys();
+ Map<String, String> 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<String, String> generatedKeys = oidcClientEndpointsResource.generateKeys();
+ Map<String, String> 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<String, String> generatedKeys = oidcClientEndpointsResource.generateKeys();
+ Map<String, String> 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<String, String> generatedKeys = oidcClientEndpointsResource.generateKeys();
+ Map<String, String> 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<String, String> generatedKeys2 = oidcClientEndpointsResource.generateKeys();
+ Map<String, String> 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 80fbe54..01859b0 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 26a740e..2da4622 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 cabe47a..e86371b 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 3884671..ca540be 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 @@
<div>
<select class="form-control" id="requestObjectSignatureAlg"
ng-change="changeRequestObjectSignatureAlg()"
- ng-model="requestObjectSignatureAlg"
- ng-options="sig for sig in requestObjectSignatureAlgorithms">
+ ng-model="requestObjectSignatureAlg">
+ <option value="any">any</option>
+ <option value="none">none</option>
+ <option ng-repeat="provider in serverInfo.listProviderIds('clientSignature')" value="{{provider}}">{{provider}}</option>
</select>
</div>
</div>