KEYCLOAK-8460 Request Object Signature Verification Other Than RS256 (#5603)
* KEYCLOAK-8460 Request Object Signature Verification Other Than RS256 also support client signed signature verification by refactored token verification mechanism * KEYCLOAK-8460 Request Object Signature Verification Other Than RS256 incorporate feedbacks and refactor client public key loading mechanism * KEYCLOAK-8460 Request Object Signature Verification Other Than RS256 unsigned request object not allowed * KEYCLOAK-8460 Request Object Signature Verification Other Than RS256 revert to re-support "none"
This commit is contained in:
parent
461dae20de
commit
0793234c19
38 changed files with 822 additions and 110 deletions
|
@ -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";
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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() {
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
org.keycloak.crypto.SignatureSpi
|
||||
org.keycloak.crypto.ClientSignatureVerifierSpi
|
|
@ -41,4 +41,6 @@ public interface TokenManager {
|
|||
|
||||
String signatureAlgorithm(TokenCategory category);
|
||||
|
||||
<T> T decodeClientJWT(String token, ClientModel client, Class<T> clazz);
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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> 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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue