KEYCLOAK-16679 Add algorithm settings for client assertion signature in OIDC identity broker

This commit is contained in:
i7a7467 2020-12-16 23:26:35 +09:00 committed by Marek Posolda
parent c4bf8ecdf0
commit b83064b142
7 changed files with 179 additions and 3 deletions

View file

@ -438,14 +438,16 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
if (getConfig().getClientAuthMethod().equals(OIDCLoginProtocol.CLIENT_SECRET_JWT)) {
try (VaultStringSecret vaultStringSecret = session.vault().getStringSecret(getConfig().getClientSecret())) {
KeyWrapper key = new KeyWrapper();
key.setAlgorithm(Algorithm.HS256);
String alg = getConfig().getClientAssertionSigningAlg() != null ? getConfig().getClientAssertionSigningAlg() : Algorithm.HS256;
key.setAlgorithm(alg);
byte[] decodedSecret = vaultStringSecret.get().orElse(getConfig().getClientSecret()).getBytes();
SecretKey secret = new SecretKeySpec(decodedSecret, 0, decodedSecret.length, Algorithm.HS256);
SecretKey secret = new SecretKeySpec(decodedSecret, 0, decodedSecret.length, alg);
key.setSecretKey(secret);
return new MacSignatureSignerContext(key);
}
}
return new AsymmetricSignatureProvider(session, Algorithm.RS256).signer();
String alg = getConfig().getClientAssertionSigningAlg() != null ? getConfig().getClientAssertionSigningAlg() : Algorithm.RS256;
return new AsymmetricSignatureProvider(session, alg).signer();
}
protected class Endpoint {

View file

@ -147,6 +147,14 @@ public class OAuth2IdentityProviderConfig extends IdentityProviderModel {
return getConfig().put(PKCE_METHOD, method);
}
public String getClientAssertionSigningAlg() {
return getConfig().get("clientAssertionSigningAlg");
}
public void setClientAssertionSigningAlg(String signingAlg) {
getConfig().put("clientAssertionSigningAlg", signingAlg);
}
@Override
public void validate(RealmModel realm) {
SslRequired sslRequired = realm.getSslRequired();

View file

@ -0,0 +1,58 @@
package org.keycloak.testsuite.broker;
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;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.keycloak.authentication.authenticators.client.JWTClientSecretAuthenticator;
import org.keycloak.crypto.Algorithm;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
public class KcOidcBrokerClientSecretJwtCustomSignAlgTest extends AbstractBrokerTest {
@Override
protected BrokerConfiguration getBrokerConfiguration() {
return new KcOidcBrokerConfigurationWithJWTAuthentication();
}
private class KcOidcBrokerConfigurationWithJWTAuthentication extends KcOidcBrokerConfiguration {
String clientSecret = UUID.randomUUID().toString();
String signAlg = Algorithm.HS384;
@Override
public List<ClientRepresentation> createProviderClients() {
List<ClientRepresentation> clientsRepList = super.createProviderClients();
log.info("Update provider clients to accept JWT authentication");
for (ClientRepresentation client : clientsRepList) {
if (client.getAttributes() == null) {
client.setAttributes(new HashMap<String, String>());
}
client.setClientAuthenticatorType(JWTClientSecretAuthenticator.PROVIDER_ID);
client.setSecret(clientSecret);
client.getAttributes().put(OIDCConfigAttributes.TOKEN_ENDPOINT_AUTH_SIGNING_ALG, signAlg);
}
return clientsRepList;
}
@Override
public IdentityProviderRepresentation setUpIdentityProvider(IdentityProviderSyncMode syncMode) {
IdentityProviderRepresentation idp = createIdentityProvider(IDP_OIDC_ALIAS, IDP_OIDC_PROVIDER_ID);
Map<String, String> config = idp.getConfig();
applyDefaultConfiguration(config, syncMode);
config.put("clientAuthMethod", OIDCLoginProtocol.CLIENT_SECRET_JWT);
config.put("clientSecret", clientSecret);
config.put("clientAssertionSigningAlg", signAlg);
return idp;
}
}
}

View file

@ -0,0 +1,92 @@
package org.keycloak.testsuite.broker;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.crypto.Algorithm;
import org.keycloak.keys.GeneratedEcdsaKeyProviderFactory;
import org.keycloak.keys.KeyProvider;
import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.testsuite.util.TokenSignatureUtil;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.ws.rs.core.Response;
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.BrokerTestConstants.REALM_CONS_NAME;
import static org.keycloak.testsuite.broker.BrokerTestTools.createIdentityProvider;
import static org.keycloak.testsuite.broker.BrokerTestTools.getConsumerRoot;
public class KcOidcBrokerPrivateKeyJwtCustomSignAlgTest extends AbstractBrokerTest {
@Override
protected BrokerConfiguration getBrokerConfiguration() {
return new KcOidcBrokerConfigurationWithJWTAuthentication();
}
private class KcOidcBrokerConfigurationWithJWTAuthentication extends KcOidcBrokerConfiguration {
String signAlg = Algorithm.ES256;
@Override
public List<ClientRepresentation> createProviderClients() {
List<ClientRepresentation> clientsRepList = super.createProviderClients();
log.info("Update provider clients to accept JWT authentication");
for (ClientRepresentation client: clientsRepList) {
client.setClientAuthenticatorType(JWTClientAuthenticator.PROVIDER_ID);
if (client.getAttributes() == null) {
client.setAttributes(new HashMap<String, String>());
}
client.getAttributes().put(OIDCConfigAttributes.TOKEN_ENDPOINT_AUTH_SIGNING_ALG, signAlg);
client.getAttributes().put(OIDCConfigAttributes.USE_JWKS_URL, "true");
client.getAttributes().put(OIDCConfigAttributes.JWKS_URL, getConsumerRoot() +
"/auth/realms/" + REALM_CONS_NAME + "/protocol/openid-connect/certs");
}
return clientsRepList;
}
@Override
public IdentityProviderRepresentation setUpIdentityProvider(IdentityProviderSyncMode syncMode) {
generateEcdsaKeyProvider("valid", signAlg, REALM_CONS_NAME, adminClient);
IdentityProviderRepresentation idp = createIdentityProvider(IDP_OIDC_ALIAS, IDP_OIDC_PROVIDER_ID);
Map<String, String> config = idp.getConfig();
applyDefaultConfiguration(config, syncMode);
config.put("clientSecret", null);
config.put("clientAuthMethod", OIDCLoginProtocol.PRIVATE_KEY_JWT);
config.put("clientAssertionSigningAlg", signAlg);
return idp;
}
private void generateEcdsaKeyProvider(String name, String alg, String realmName, Keycloak adminClient) {
ComponentRepresentation rep = createRep(name,
adminClient.realm(realmName).toRepresentation().getId(), GeneratedEcdsaKeyProviderFactory.ID);
long priority = System.currentTimeMillis();
rep.getConfig().putSingle("priority", Long.toString(priority));
rep.getConfig().putSingle("active", "true");
rep.getConfig().putSingle("enabled", "true");
rep.getConfig().putSingle("ecdsaEllipticCurveKey",
TokenSignatureUtil.convertAlgorithmToECDomainParamNistRep(alg));
Response response = adminClient.realm(realmName).components().add(rep);
response.close();
}
protected ComponentRepresentation createRep(String name, String realmId, String providerId) {
ComponentRepresentation rep = new ComponentRepresentation();
rep.setName(name);
rep.setParentId(realmId);
rep.setProviderId(providerId);
rep.setProviderType(KeyProvider.class.getName());
rep.setConfig(new MultivaluedHashMap<>());
return rep;
}
}
}

View file

@ -629,6 +629,8 @@ client-secret=クライアント・シークレット
show-secret=シークレットを表示する
hide-secret=シークレットを隠す
client-secret.tooltip=アイデンティティー・プロバイダーで登録されているクライアントまたはクライアント・シークレットを設定します。このフィールドは、ボールトから値を取得できます。${vault.ID}形式を使用します。
client-assertion-signing-algorithm=クライアントアサーション署名アルゴリズム
client-assertion-signing-algorithm.tooltip=クライアント認証でJWTアサーションを利用するときの署名アルゴリズム。クライアント認証が 秘密鍵で署名されたJWT もしくは JWTでクライアント・シークレット の場合に設定します。アルゴリズムの指定をしなかった場合、 秘密鍵で署名されたJWT ではRS256 JWTでクライアント・シークレット ではHS256のアルゴリズムが使用されます。
issuer=発行者Issuer
issuer.tooltip=レスポンス内の発行者の識別子Issuer Identifierを設定します。未設定の場合は、検証は実行されません。
default-scopes=デフォルト・スコープ

View file

@ -652,6 +652,8 @@ 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
client-assertion-signing-algorithm=Client Assertion Signature Algorithm
client-assertion-signing-algorithm.tooltip=Signature algorithm to create JWT assertion as client authentication. In the case of JWT signed with private key or Client secret as jwt, it is required. If no algorithm is specified, the following algorithm is adapted. RS256 is adapted in the case of JWT signed with private key. HS256 is adapted in the case of Client secret as jwt.
show-secret=Show secret
hide-secret=Hide secret
client-secret.tooltip=The client or client secret registered within the identity provider. This field is able to obtain its value from vault, use ${vault.ID} format.

View file

@ -210,6 +210,18 @@
<input class="form-control" id="clientSecret" kc-password ng-model="identityProvider.config.clientSecret" ng-required="identityProvider.config.clientAuthMethod != 'private_key_jwt'">
</div>
<kc-tooltip>{{:: 'client-secret.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="clientAssertionSigningAlg"><span data-ng-show="identityProvider.config.clientAuthMethod == 'private_key_jwt' || identityProvider.config.clientAuthMethod == 'client_secret_jwt'" class="required">*</span> {{:: 'client-assertion-signing-algorithm' | translate}}</label>
<div class="col-md-6">
<select class="form-control" id="clientAssertionSigningAlg" ng-required="identityProvider.config.clientAuthMethod == 'private_key_jwt' || identityProvider.config.clientAuthMethod == 'client_secret_jwt'"
ng-model="identityProvider.config.clientAssertionSigningAlg">
<option value=""></option>
<option ng-repeat="provider in serverInfo.listProviderIds('signature')" value="{{provider}}">
{{provider}}</option>
</select>
</div>
<kc-tooltip>{{:: 'client-assertion-signing-algorithm.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="issuer">{{:: 'issuer' | translate}} </label>