diff --git a/services/src/main/java/org/keycloak/keys/loader/HardcodedPublicKeyLoader.java b/services/src/main/java/org/keycloak/keys/loader/HardcodedPublicKeyLoader.java new file mode 100644 index 0000000000..b5cbbdec39 --- /dev/null +++ b/services/src/main/java/org/keycloak/keys/loader/HardcodedPublicKeyLoader.java @@ -0,0 +1,51 @@ +/* + * Copyright 2017 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.keys.loader; + +import org.keycloak.common.util.PemUtils; +import org.keycloak.keys.PublicKeyLoader; + +import java.security.PublicKey; +import java.util.*; + +/** + * + * @author hmlnarik + */ +public class HardcodedPublicKeyLoader implements PublicKeyLoader { + + private final String kid; + private final String pem; + + public HardcodedPublicKeyLoader(String kid, String pem) { + this.kid = kid; + this.pem = pem; + } + + @Override + public Map loadKeys() throws Exception { + return Collections.unmodifiableMap(Collections.singletonMap(kid, getSavedPublicKey())); + } + + protected PublicKey getSavedPublicKey() { + if (pem != null && ! pem.trim().equals("")) { + return PemUtils.decodePublicKey(pem); + } else { + return null; + } + } +} 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 d4507e9761..30aae3f0c0 100644 --- a/services/src/main/java/org/keycloak/keys/loader/PublicKeyStorageManager.java +++ b/services/src/main/java/org/keycloak/keys/loader/PublicKeyStorageManager.java @@ -21,17 +21,20 @@ import java.security.PublicKey; import org.keycloak.broker.oidc.OIDCIdentityProviderConfig; import org.keycloak.jose.jws.JWSInput; -import org.keycloak.keys.PublicKeyStorageProvider; -import org.keycloak.keys.PublicKeyStorageUtils; +import org.keycloak.keys.*; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; +import org.jboss.logging.Logger; + /** * @author Marek Posolda */ 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(); @@ -44,13 +47,31 @@ public class PublicKeyStorageManager { public static PublicKey getIdentityProviderPublicKey(KeycloakSession session, RealmModel realm, OIDCIdentityProviderConfig idpConfig, JWSInput input) { + boolean keyIdSetInConfiguration = idpConfig.getPublicKeySignatureVerifierKeyId() != null + && ! idpConfig.getPublicKeySignatureVerifierKeyId().trim().isEmpty(); + String kid = input.getHeader().getKeyId(); PublicKeyStorageProvider keyStorage = session.getProvider(PublicKeyStorageProvider.class); String modelKey = PublicKeyStorageUtils.getIdpModelCacheKey(realm.getId(), idpConfig.getInternalId()); - OIDCIdentityProviderPublicKeyLoader loader = new OIDCIdentityProviderPublicKeyLoader(session, idpConfig); + PublicKeyLoader loader; + if (idpConfig.isUseJwksUrl()) { + loader = new OIDCIdentityProviderPublicKeyLoader(session, idpConfig); + } else { + String pem = idpConfig.getPublicKeySignatureVerifier(); + + if (pem == null || pem.trim().isEmpty()) { + logger.warnf("No public key saved on identityProvider %s", idpConfig.getAlias()); + return null; + } + + loader = new HardcodedPublicKeyLoader( + keyIdSetInConfiguration + ? idpConfig.getPublicKeySignatureVerifierKeyId().trim() + : kid, pem); + } + return keyStorage.getPublicKey(modelKey, kid, loader); } - } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOIDCBrokerWithSignatureTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOIDCBrokerWithSignatureTest.java index 6bc60b1aea..e7d8d44a5b 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOIDCBrokerWithSignatureTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOIDCBrokerWithSignatureTest.java @@ -211,6 +211,23 @@ public class KcOIDCBrokerWithSignatureTest extends AbstractBaseBrokerTest { // Set key id to a valid one cfg.setPublicKeySignatureVerifierKeyId(expectedKeyId); updateIdentityProvider(idpRep); + logInAsUserInIDP(); + assertLoggedInAccountManagement(); + logoutFromRealm(bc.consumerRealmName()); + + // Set key id to empty + cfg.setPublicKeySignatureVerifierKeyId(""); + updateIdentityProvider(idpRep); + logInAsUserInIDP(); + assertLoggedInAccountManagement(); + logoutFromRealm(bc.consumerRealmName()); + + // Unset key id + cfg.setPublicKeySignatureVerifierKeyId(null); + updateIdentityProvider(idpRep); + logInAsUserInIDP(); + assertLoggedInAccountManagement(); + logoutFromRealm(bc.consumerRealmName()); } diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties index b6d285b1db..d047b773c2 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties @@ -504,7 +504,7 @@ identity-provider.jwks-url.tooltip=URL where identity provider keys in JWK forma validating-public-key=Validating Public Key identity-provider.validating-public-key.tooltip=The public key in PEM format that must be used to verify external IDP signatures. validating-public-key-id=Validating Public Key Id -identity-provider.validating-public-key-id.tooltip=Explicit ID of the validating public key given above if the key ID. Leave unset if the external IDP is Keycloak or uses the same mechanism to determine key ID. +identity-provider.validating-public-key-id.tooltip=Explicit ID of the validating public key given above if the key ID. Leave blank if the key above should be used always, regardless of key ID specified by external IDP; set it if the key should only be used for verifying if key ID from external IDP matches. import-external-idp-config=Import External IDP Config import-external-idp-config.tooltip=Allows you to load external IDP metadata from a config file or to download it from a URL. import-from-url=Import from URL