Skip to content

Commit

Permalink
KEYCLOAK-16679 Add algorithm settings for client assertion signature …
Browse files Browse the repository at this point in the history
…in OIDC identity broker
  • Loading branch information
i7a7467 authored and mposolda committed Mar 1, 2021
1 parent c4bf8ec commit b83064b
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -438,14 +438,16 @@ protected SignatureSignerContext getSignatureContext() {
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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,14 @@ public String setPkceMethod(String method) {
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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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=デフォルト・スコープ
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down

0 comments on commit b83064b

Please sign in to comment.