From c35718cb87e898f98e20ac233c9864b8b4c47336 Mon Sep 17 00:00:00 2001 From: madgaet Date: Wed, 11 Sep 2019 00:08:34 +0200 Subject: [PATCH] [KEYCLOAK-9809] Support private_key_jwt authentication for external IdP --- .../oidc/AbstractOAuth2IdentityProvider.java | 94 ++++++++++++++----- .../oidc/OAuth2IdentityProviderConfig.java | 17 ++++ .../testsuite/admin/IdentityProviderTest.java | 30 ++++++ .../KcOidcBrokerClientSecretJwtTest.java | 48 ++++++++++ .../broker/KcOidcBrokerPrivateKeyJwtTest.java | 72 ++++++++++++++ .../messages/admin-messages_en.properties | 6 ++ .../realm-identity-provider-oidc.html | 20 +++- 7 files changed, 261 insertions(+), 26 deletions(-) create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerClientSecretJwtTest.java create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerPrivateKeyJwtTest.java diff --git a/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java b/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java index 8c31e1eac8..b48a5f0d09 100755 --- a/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java +++ b/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java @@ -30,10 +30,17 @@ import org.keycloak.broker.provider.ExchangeTokenToIdentityProviderToken; import org.keycloak.broker.provider.IdentityProvider; import org.keycloak.broker.provider.util.SimpleHttp; import org.keycloak.common.ClientConnection; +import org.keycloak.common.util.Time; +import org.keycloak.crypto.Algorithm; +import org.keycloak.crypto.AsymmetricSignatureProvider; +import org.keycloak.crypto.KeyWrapper; +import org.keycloak.crypto.MacSignatureSignerContext; +import org.keycloak.crypto.SignatureSignerContext; import org.keycloak.events.Details; import org.keycloak.events.Errors; import org.keycloak.events.EventBuilder; import org.keycloak.events.EventType; +import org.keycloak.jose.jws.JWSBuilder; import org.keycloak.models.ClientModel; import org.keycloak.models.FederatedIdentityModel; import org.keycloak.models.KeycloakSession; @@ -41,14 +48,18 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; import org.keycloak.representations.AccessTokenResponse; +import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.endpoints.AuthorizationEndpoint; +import org.keycloak.representations.JsonWebToken; import org.keycloak.services.ErrorPage; import org.keycloak.services.ErrorResponseException; import org.keycloak.services.messages.Messages; import org.keycloak.sessions.AuthenticationSessionModel; import org.keycloak.vault.VaultStringSecret; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; import javax.ws.rs.GET; import javax.ws.rs.QueryParam; import javax.ws.rs.WebApplicationException; @@ -127,20 +138,20 @@ public abstract class AbstractOAuth2IdentityProvider createProviderClients(SuiteContext suiteContext) { + List clientsRepList = super.createProviderClients(suiteContext); + log.info("Update provider clients to accept JWT authentication"); + for (ClientRepresentation client: clientsRepList) { + client.setClientAuthenticatorType(JWTClientSecretAuthenticator.PROVIDER_ID); + client.setSecret(CLIENT_SECRET); + } + return clientsRepList; + } + + @Override + public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext) { + IdentityProviderRepresentation idp = createIdentityProvider(IDP_OIDC_ALIAS, IDP_OIDC_PROVIDER_ID); + Map config = idp.getConfig(); + applyDefaultConfiguration(suiteContext, config); + config.put("clientAuthMethod", OIDCLoginProtocol.CLIENT_SECRET_JWT); + return idp; + } + + + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerPrivateKeyJwtTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerPrivateKeyJwtTest.java new file mode 100644 index 0000000000..c51633c389 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerPrivateKeyJwtTest.java @@ -0,0 +1,72 @@ +/* + * 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.testsuite.broker; + +import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator; +import org.keycloak.crypto.Algorithm; +import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.IdentityProviderRepresentation; +import org.keycloak.representations.idm.KeysMetadataRepresentation.KeyMetadataRepresentation; +import org.keycloak.testsuite.arquillian.SuiteContext; +import org.keycloak.testsuite.util.KeyUtils; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.keycloak.testsuite.broker.BrokerTestConstants.IDP_OIDC_ALIAS; +import static org.keycloak.testsuite.broker.BrokerTestConstants.IDP_OIDC_PROVIDER_ID; +import static org.keycloak.testsuite.broker.BrokerTestTools.createIdentityProvider; + +public class KcOidcBrokerPrivateKeyJwtTest extends KcOidcBrokerTest { + + @Override + protected BrokerConfiguration getBrokerConfiguration() { + return new KcOidcBrokerConfigurationWithJWTAuthentication(); + } + + private class KcOidcBrokerConfigurationWithJWTAuthentication extends KcOidcBrokerConfiguration { + + @Override + public List createProviderClients(SuiteContext suiteContext) { + List clientsRepList = super.createProviderClients(suiteContext); + log.info("Update provider clients to accept JWT authentication"); + KeyMetadataRepresentation keyRep = KeyUtils.getActiveKey(adminClient.realm(consumerRealmName()).keys().getKeyMetadata(), Algorithm.RS256); + for (ClientRepresentation client: clientsRepList) { + client.setClientAuthenticatorType(JWTClientAuthenticator.PROVIDER_ID); + if (client.getAttributes() == null) { + client.setAttributes(new HashMap()); + } + client.getAttributes().put(JWTClientAuthenticator.CERTIFICATE_ATTR, keyRep.getCertificate()); + } + return clientsRepList; + } + + @Override + public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext) { + IdentityProviderRepresentation idp = createIdentityProvider(IDP_OIDC_ALIAS, IDP_OIDC_PROVIDER_ID); + Map config = idp.getConfig(); + applyDefaultConfiguration(suiteContext, config); + config.put("clientSecret", null); + config.put("clientAuthMethod", OIDCLoginProtocol.PRIVATE_KEY_JWT); + return idp; + } + + } + +} \ No newline at end of file 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 0114088cda..6f53fa41e2 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 @@ -581,6 +581,12 @@ backchannel-logout=Backchannel Logout backchannel-logout.tooltip=Does the external IDP support backchannel logout? user-info-url=User Info URL user-info-url.tooltip=The User Info Url. This is optional. +client-auth=Client Authentication +client-auth.tooltip=The client authentication methdod (cfr. https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication). +client-auth.client_secret_post=Client secret sent as post +client-auth.client_secret_basic=Client secret sent as basic auth +client-auth.client_secret_jwt=Client secret as jwt +client-auth.private_key_jwt=JWT signed with private key identity-provider.client-id.tooltip=The client or client identifier registered within the identity provider. client-secret=Client Secret show-secret=Show secret diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html index d1918e74e8..d755805b57 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html @@ -166,6 +166,22 @@ {{:: 'user-info-url.tooltip' | translate}} +
+ +
+
+ +
+
+ {{:: 'client-auth.tooltip' | translate}} +
@@ -174,9 +190,9 @@ {{:: 'identity-provider.client-id.tooltip' | translate}}
- +
- +
{{:: 'client-secret.tooltip' | translate}}