KEYCLOAK-14202 Client Policy - Executor : Enforce secure signature algorithm for Signed JWT client authentication
Co-authored-by: Andrii Murashkin <amu@adorsys.com.ua>
This commit is contained in:
parent
ca7c866eb4
commit
05dfac75ca
4 changed files with 451 additions and 1 deletions
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* Copyright 2020 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.services.clientpolicy.executor;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.spi.HttpRequest;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.crypto.Algorithm;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.jose.jws.JWSInputException;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.services.clientpolicy.ClientPolicyContext;
|
||||
import org.keycloak.services.clientpolicy.ClientPolicyException;
|
||||
import org.keycloak.services.clientpolicy.ClientPolicyLogger;
|
||||
|
||||
public class SecureSigningAlgorithmForSignedJwtEnforceExecutor implements ClientPolicyExecutorProvider {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(SecureSigningAlgorithmForSignedJwtEnforceExecutor.class);
|
||||
|
||||
private final KeycloakSession session;
|
||||
private final ComponentModel componentModel;
|
||||
|
||||
public SecureSigningAlgorithmForSignedJwtEnforceExecutor(KeycloakSession session, ComponentModel componentModel) {
|
||||
this.session = session;
|
||||
this.componentModel = componentModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return componentModel.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProviderId() {
|
||||
return componentModel.getProviderId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeOnEvent(ClientPolicyContext context) throws ClientPolicyException {
|
||||
switch (context.getEvent()) {
|
||||
case TOKEN_REQUEST:
|
||||
case TOKEN_REFRESH:
|
||||
case TOKEN_REVOKE:
|
||||
case TOKEN_INTROSPECT:
|
||||
case LOGOUT_REQUEST:
|
||||
HttpRequest req = session.getContext().getContextObject(HttpRequest.class);
|
||||
String clientAssertion = req.getDecodedFormParameters().getFirst(OAuth2Constants.CLIENT_ASSERTION);
|
||||
JWSInput jws = null;
|
||||
try {
|
||||
jws = new JWSInput(clientAssertion);
|
||||
} catch (JWSInputException e) {
|
||||
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "not allowed input format.");
|
||||
}
|
||||
String alg = jws.getHeader().getAlgorithm().name();
|
||||
verifySecureSigningAlgorithm(alg);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void verifySecureSigningAlgorithm(String signatureAlgorithm) throws ClientPolicyException {
|
||||
if (signatureAlgorithm == null) {
|
||||
ClientPolicyLogger.log(logger, "Signing algorithm not specified explicitly.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Please change also SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory.getHelpText() if you are changing any algorithms here.
|
||||
switch (signatureAlgorithm) {
|
||||
case Algorithm.PS256:
|
||||
case Algorithm.PS384:
|
||||
case Algorithm.PS512:
|
||||
case Algorithm.ES256:
|
||||
case Algorithm.ES384:
|
||||
case Algorithm.ES512:
|
||||
ClientPolicyLogger.log(logger, "Passed. signatureAlgorithm = " + signatureAlgorithm);
|
||||
return;
|
||||
}
|
||||
ClientPolicyLogger.log(logger, "NOT allowed signatureAlgorithm = " + signatureAlgorithm);
|
||||
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "not allowed signature algorithm.");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright 2020 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.services.clientpolicy.executor;
|
||||
|
||||
import org.keycloak.Config.Scope;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory implements ClientPolicyExecutorProviderFactory {
|
||||
|
||||
public static final String PROVIDER_ID = "securesignalgjwt-enforce-executor";
|
||||
|
||||
@Override
|
||||
public ClientPolicyExecutorProvider create(KeycloakSession session, ComponentModel model) {
|
||||
return new SecureSigningAlgorithmForSignedJwtEnforceExecutor(session, model);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "It refuses the client whose JWT token signature algorithms are considered not to be secure. It accepts ES256, ES384, ES512, PS256, PS384 and PS512.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
}
|
|
@ -4,4 +4,5 @@ org.keycloak.services.clientpolicy.executor.SecureClientAuthEnforceExecutorFacto
|
|||
org.keycloak.services.clientpolicy.executor.PKCEEnforceExecutorFactory
|
||||
org.keycloak.services.clientpolicy.executor.SecureSessionEnforceExecutorFactory
|
||||
org.keycloak.services.clientpolicy.executor.SecureSigningAlgorithmEnforceExecutorFactory
|
||||
org.keycloak.services.clientpolicy.executor.SecureRedirectUriEnforceExecutorFactory
|
||||
org.keycloak.services.clientpolicy.executor.SecureRedirectUriEnforceExecutorFactory
|
||||
org.keycloak.services.clientpolicy.executor.SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory
|
|
@ -25,18 +25,37 @@ import static org.keycloak.testsuite.admin.ApiUtil.findUserByUsername;
|
|||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.ws.rs.BadRequestException;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.junit.After;
|
||||
|
@ -46,6 +65,7 @@ import org.junit.Rule;
|
|||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.adapters.AdapterUtils;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
|
||||
import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
|
||||
|
@ -55,13 +75,21 @@ import org.keycloak.client.registration.Auth;
|
|||
import org.keycloak.client.registration.ClientRegistration;
|
||||
import org.keycloak.client.registration.ClientRegistrationException;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.util.Base64;
|
||||
import org.keycloak.common.util.Base64Url;
|
||||
import org.keycloak.common.util.KeyUtils;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.common.util.UriUtils;
|
||||
import org.keycloak.constants.ServiceUrlConstants;
|
||||
import org.keycloak.crypto.KeyType;
|
||||
import org.keycloak.crypto.SignatureSignerContext;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.jose.jws.Algorithm;
|
||||
import org.keycloak.jose.jws.JWSBuilder;
|
||||
import org.keycloak.models.AdminRoles;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
@ -70,6 +98,7 @@ import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
|||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.JsonWebToken;
|
||||
import org.keycloak.representations.RefreshToken;
|
||||
import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
|
||||
import org.keycloak.representations.idm.ClientInitialAccessPresentation;
|
||||
|
@ -102,6 +131,7 @@ import org.keycloak.services.clientpolicy.executor.SecureRequestObjectExecutorFa
|
|||
import org.keycloak.services.clientpolicy.executor.SecureResponseTypeExecutorFactory;
|
||||
import org.keycloak.services.clientpolicy.executor.SecureSessionEnforceExecutorFactory;
|
||||
import org.keycloak.services.clientpolicy.executor.SecureSigningAlgorithmEnforceExecutorFactory;
|
||||
import org.keycloak.services.clientpolicy.executor.SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory;
|
||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
|
@ -110,6 +140,7 @@ import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
|||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
|
||||
import org.keycloak.testsuite.client.resources.TestApplicationResourceUrls;
|
||||
import org.keycloak.testsuite.client.resources.TestOIDCEndpointsApplicationResource;
|
||||
import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource;
|
||||
import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource.AuthorizationEndpointRequestObject;
|
||||
import org.keycloak.testsuite.services.clientpolicy.condition.TestRaiseExeptionConditionFactory;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
|
@ -1235,6 +1266,260 @@ public class ClientPolicyBasicsTest extends AbstractKeycloakTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecureSigningAlgorithmForSignedJwtEnforceExecutor() throws Exception {
|
||||
// policy including client role condition
|
||||
String policyName = "MyPolicy";
|
||||
createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null);
|
||||
logger.info("... Created Policy : " + policyName);
|
||||
|
||||
createCondition("ClientRolesCondition", ClientRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {
|
||||
setConditionClientRoles(provider, new ArrayList<>(Arrays.asList("sample-client-role-alpha", "sample-client-role-zeta")));
|
||||
});
|
||||
registerCondition("ClientRolesCondition", policyName);
|
||||
logger.info("... Registered Condition : " + "ClientRolesCondition");
|
||||
|
||||
createExecutor("SecureSigningAlgorithmForSignedJwtEnforceExecutor", SecureSigningAlgorithmForSignedJwtEnforceExecutorFactory.PROVIDER_ID, null,
|
||||
(ComponentRepresentation provider) -> {
|
||||
});
|
||||
|
||||
registerExecutor("SecureSigningAlgorithmForSignedJwtEnforceExecutor", policyName);
|
||||
logger.info("... Registered Executor : SecureSigningAlgorithmForSignedJwtEnforceExecutor");
|
||||
|
||||
// crate a client with client role
|
||||
String clientAlphaId = "Alpha-App";
|
||||
String clientAlphaSecret = "secretAlpha";
|
||||
String cAlphaId = null;
|
||||
|
||||
cAlphaId = createClientByAdmin(clientAlphaId, (ClientRepresentation clientRep) -> {
|
||||
clientRep.setDefaultRoles(Arrays.asList("sample-client-role-alpha", "sample-client-role-common").toArray(new String[2]));
|
||||
clientRep.setSecret(clientAlphaSecret);
|
||||
clientRep.setClientAuthenticatorType(JWTClientAuthenticator.PROVIDER_ID);
|
||||
clientRep.setAttributes(new HashMap<>());
|
||||
clientRep.getAttributes().put(OIDCConfigAttributes.TOKEN_ENDPOINT_AUTH_SIGNING_ALG, org.keycloak.crypto.Algorithm.ES256);
|
||||
});
|
||||
|
||||
try {
|
||||
ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm(REALM_NAME), clientAlphaId);
|
||||
ClientRepresentation clientRep = clientResource.toRepresentation();
|
||||
|
||||
KeyPair keyPair = setupJwks(org.keycloak.crypto.Algorithm.ES256, clientRep, clientResource);
|
||||
PublicKey publicKey = keyPair.getPublic();
|
||||
PrivateKey privateKey = keyPair.getPrivate();
|
||||
|
||||
successfulLoginAndLogoutWithSignedJWT(clientAlphaId, privateKey, publicKey);
|
||||
} finally {
|
||||
deleteClientByAdmin(cAlphaId);
|
||||
}
|
||||
}
|
||||
|
||||
private CloseableHttpResponse sendRequest(String requestUrl, List<NameValuePair> parameters) throws Exception {
|
||||
CloseableHttpClient client = new DefaultHttpClient();
|
||||
try {
|
||||
HttpPost post = new HttpPost(requestUrl);
|
||||
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(parameters, "UTF-8");
|
||||
post.setEntity(formEntity);
|
||||
return client.execute(post);
|
||||
} finally {
|
||||
oauth.closeClient(client);
|
||||
}
|
||||
}
|
||||
|
||||
private void successfulLoginAndLogoutWithSignedJWT(String clientId, PrivateKey privateKey, PublicKey publicKey) throws Exception {
|
||||
String signedJwt = createSignedRequestToken(clientId, getRealmInfoUrl(), privateKey, publicKey, org.keycloak.crypto.Algorithm.ES256);
|
||||
|
||||
oauth.clientId(clientId);
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
EventRepresentation loginEvent = events.expectLogin()
|
||||
.client(clientId)
|
||||
.assertEvent();
|
||||
String sessionId = loginEvent.getSessionId();
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
|
||||
//obtain access token
|
||||
OAuthClient.AccessTokenResponse response = doAccessTokenRequestWithSignedJWT(code, signedJwt);
|
||||
|
||||
assertEquals(200, response.getStatusCode());
|
||||
oauth.verifyToken(response.getAccessToken());
|
||||
RefreshToken refreshToken = oauth.parseRefreshToken(response.getRefreshToken());
|
||||
assertEquals(sessionId, refreshToken.getSessionState());
|
||||
assertEquals(sessionId, refreshToken.getSessionState());
|
||||
events.expectCodeToToken(loginEvent.getDetails().get(Details.CODE_ID), loginEvent.getSessionId())
|
||||
.client(clientId)
|
||||
.detail(Details.CLIENT_AUTH_METHOD, JWTClientAuthenticator.PROVIDER_ID)
|
||||
.assertEvent();
|
||||
|
||||
//refresh token
|
||||
signedJwt = createSignedRequestToken(clientId, getRealmInfoUrl(), privateKey, publicKey, org.keycloak.crypto.Algorithm.ES256);
|
||||
OAuthClient.AccessTokenResponse refreshedResponse = doRefreshTokenRequestWithSignedJWT(response.getRefreshToken(), signedJwt);
|
||||
assertEquals(200, refreshedResponse.getStatusCode());
|
||||
|
||||
//introspect token
|
||||
signedJwt = createSignedRequestToken(clientId, getRealmInfoUrl(), privateKey, publicKey, org.keycloak.crypto.Algorithm.ES256);
|
||||
HttpResponse tokenIntrospectionResponse = doTokenIntrospectionWithSignedJWT("access_token", refreshedResponse.getAccessToken(), signedJwt);
|
||||
assertEquals(200, tokenIntrospectionResponse.getStatusLine().getStatusCode());
|
||||
|
||||
//revoke token
|
||||
signedJwt = createSignedRequestToken(clientId, getRealmInfoUrl(), privateKey, publicKey, org.keycloak.crypto.Algorithm.ES256);
|
||||
HttpResponse revokeTokenResponse = doTokenRevokeWithSignedJWT("refresh_toke", refreshedResponse.getRefreshToken(), signedJwt);
|
||||
assertEquals(200, revokeTokenResponse.getStatusLine().getStatusCode());
|
||||
|
||||
signedJwt = createSignedRequestToken(clientId, getRealmInfoUrl(), privateKey, publicKey, org.keycloak.crypto.Algorithm.ES256);
|
||||
OAuthClient.AccessTokenResponse tokenRes = doRefreshTokenRequestWithSignedJWT(refreshedResponse.getRefreshToken(), signedJwt);
|
||||
assertEquals(400, tokenRes.getStatusCode());
|
||||
assertEquals(OAuthErrorException.INVALID_GRANT, tokenRes.getError());
|
||||
|
||||
//logout
|
||||
signedJwt = createSignedRequestToken(clientId, getRealmInfoUrl(), privateKey, publicKey, org.keycloak.crypto.Algorithm.ES256);
|
||||
HttpResponse logoutResponse = doLogoutWithSignedJWT(refreshedResponse.getRefreshToken(), signedJwt);
|
||||
assertEquals(204, logoutResponse.getStatusLine().getStatusCode());
|
||||
|
||||
}
|
||||
|
||||
private KeyPair setupJwks(String algorithm, ClientRepresentation clientRepresentation, ClientResource clientResource) throws Exception {
|
||||
// generate and register client keypair
|
||||
TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
|
||||
oidcClientEndpointsResource.generateKeys(algorithm);
|
||||
Map<String, String> generatedKeys = oidcClientEndpointsResource.getKeysAsBase64();
|
||||
KeyPair keyPair = getKeyPairFromGeneratedBase64(generatedKeys, algorithm);
|
||||
|
||||
// use and set jwks_url
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRepresentation).setUseJwksUrl(true);
|
||||
String jwksUrl = TestApplicationResourceUrls.clientJwksUri();
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRepresentation).setJwksUrl(jwksUrl);
|
||||
clientResource.update(clientRepresentation);
|
||||
|
||||
// set time offset, so that new keys are downloaded
|
||||
setTimeOffset(20);
|
||||
|
||||
return keyPair;
|
||||
}
|
||||
|
||||
private KeyPair getKeyPairFromGeneratedBase64(Map<String, String> generatedKeys, String algorithm) throws Exception {
|
||||
// It seems that PemUtils.decodePrivateKey, decodePublicKey can only treat RSA type keys, not EC type keys. Therefore, these are not used.
|
||||
String privateKeyBase64 = generatedKeys.get(TestingOIDCEndpointsApplicationResource.PRIVATE_KEY);
|
||||
String publicKeyBase64 = generatedKeys.get(TestingOIDCEndpointsApplicationResource.PUBLIC_KEY);
|
||||
PrivateKey privateKey = decodePrivateKey(Base64.decode(privateKeyBase64), algorithm);
|
||||
PublicKey publicKey = decodePublicKey(Base64.decode(publicKeyBase64), algorithm);
|
||||
return new KeyPair(publicKey, privateKey);
|
||||
}
|
||||
|
||||
private static PrivateKey decodePrivateKey(byte[] der, String algorithm) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException {
|
||||
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(der);
|
||||
String keyAlg = getKeyAlgorithmFromJwaAlgorithm(algorithm);
|
||||
KeyFactory kf = KeyFactory.getInstance(keyAlg, "BC");
|
||||
return kf.generatePrivate(spec);
|
||||
}
|
||||
|
||||
private static PublicKey decodePublicKey(byte[] der, String algorithm) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException {
|
||||
X509EncodedKeySpec spec = new X509EncodedKeySpec(der);
|
||||
String keyAlg = getKeyAlgorithmFromJwaAlgorithm(algorithm);
|
||||
KeyFactory kf = KeyFactory.getInstance(keyAlg, "BC");
|
||||
return kf.generatePublic(spec);
|
||||
}
|
||||
|
||||
private String createSignedRequestToken(String clientId, String realmInfoUrl, PrivateKey privateKey, PublicKey publicKey, String algorithm) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
|
||||
JsonWebToken jwt = createRequestToken(clientId, realmInfoUrl);
|
||||
String kid = KeyUtils.createKeyId(publicKey);
|
||||
SignatureSignerContext signer = oauth.createSigner(privateKey, kid, algorithm);
|
||||
return new JWSBuilder().kid(kid).jsonContent(jwt).sign(signer);
|
||||
}
|
||||
|
||||
private OAuthClient.AccessTokenResponse doAccessTokenRequestWithSignedJWT(String code, String signedJwt) throws Exception {
|
||||
List<NameValuePair> parameters = new LinkedList<>();
|
||||
parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.AUTHORIZATION_CODE));
|
||||
parameters.add(new BasicNameValuePair(OAuth2Constants.CODE, code));
|
||||
parameters.add(new BasicNameValuePair(OAuth2Constants.REDIRECT_URI, oauth.getRedirectUri()));
|
||||
parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION_TYPE, OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT));
|
||||
parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION, signedJwt));
|
||||
|
||||
CloseableHttpResponse response = sendRequest(oauth.getAccessTokenUrl(), parameters);
|
||||
return new OAuthClient.AccessTokenResponse(response);
|
||||
}
|
||||
|
||||
private OAuthClient.AccessTokenResponse doRefreshTokenRequestWithSignedJWT(String refreshToken, String signedJwt) throws Exception {
|
||||
List<NameValuePair> parameters = new LinkedList<>();
|
||||
parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.REFRESH_TOKEN));
|
||||
parameters.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, refreshToken));
|
||||
parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION_TYPE, OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT));
|
||||
parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION, signedJwt));
|
||||
|
||||
CloseableHttpResponse response = sendRequest(oauth.getRefreshTokenUrl(), parameters);
|
||||
return new OAuthClient.AccessTokenResponse(response);
|
||||
}
|
||||
|
||||
private HttpResponse doTokenIntrospectionWithSignedJWT(String tokenType, String tokenToIntrospect, String signedJwt) throws Exception {
|
||||
List<NameValuePair> parameters = new LinkedList<>();
|
||||
parameters.add(new BasicNameValuePair("token", tokenToIntrospect));
|
||||
parameters.add(new BasicNameValuePair("token_type_hint", tokenType));
|
||||
parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION_TYPE, OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT));
|
||||
parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION, signedJwt));
|
||||
|
||||
return sendRequest(oauth.getTokenIntrospectionUrl(), parameters);
|
||||
}
|
||||
|
||||
private HttpResponse doTokenRevokeWithSignedJWT(String tokenType, String tokenToIntrospect, String signedJwt) throws Exception {
|
||||
List<NameValuePair> parameters = new LinkedList<>();
|
||||
parameters.add(new BasicNameValuePair("token", tokenToIntrospect));
|
||||
parameters.add(new BasicNameValuePair("token_type_hint", tokenType));
|
||||
parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION_TYPE, OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT));
|
||||
parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION, signedJwt));
|
||||
|
||||
return sendRequest(oauth.getTokenRevocationUrl(), parameters);
|
||||
}
|
||||
|
||||
private HttpResponse doLogoutWithSignedJWT(String refreshToken, String signedJwt) throws Exception {
|
||||
List<NameValuePair> parameters = new LinkedList<>();
|
||||
parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.REFRESH_TOKEN));
|
||||
parameters.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, refreshToken));
|
||||
parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION_TYPE, OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT));
|
||||
parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION, signedJwt));
|
||||
|
||||
return sendRequest(oauth.getLogoutUrl().build(), parameters);
|
||||
}
|
||||
|
||||
private JsonWebToken createRequestToken(String clientId, String realmInfoUrl) {
|
||||
JsonWebToken reqToken = new JsonWebToken();
|
||||
reqToken.id(AdapterUtils.generateId());
|
||||
reqToken.issuer(clientId);
|
||||
reqToken.subject(clientId);
|
||||
reqToken.audience(realmInfoUrl);
|
||||
|
||||
int now = Time.currentTime();
|
||||
reqToken.issuedAt(now);
|
||||
reqToken.expiration(now + 10);
|
||||
reqToken.notBefore(now);
|
||||
|
||||
return reqToken;
|
||||
}
|
||||
|
||||
private static String getKeyAlgorithmFromJwaAlgorithm(String jwaAlgorithm) {
|
||||
String keyAlg = null;
|
||||
switch (jwaAlgorithm) {
|
||||
case org.keycloak.crypto.Algorithm.RS256:
|
||||
case org.keycloak.crypto.Algorithm.RS384:
|
||||
case org.keycloak.crypto.Algorithm.RS512:
|
||||
case org.keycloak.crypto.Algorithm.PS256:
|
||||
case org.keycloak.crypto.Algorithm.PS384:
|
||||
case org.keycloak.crypto.Algorithm.PS512:
|
||||
keyAlg = KeyType.RSA;
|
||||
break;
|
||||
case org.keycloak.crypto.Algorithm.ES256:
|
||||
case org.keycloak.crypto.Algorithm.ES384:
|
||||
case org.keycloak.crypto.Algorithm.ES512:
|
||||
keyAlg = KeyType.EC;
|
||||
break;
|
||||
default :
|
||||
throw new RuntimeException("Unsupported signature algorithm");
|
||||
}
|
||||
return keyAlg;
|
||||
}
|
||||
|
||||
private String getRealmInfoUrl() {
|
||||
String authServerBaseUrl = UriUtils.getOrigin(oauth.getRedirectUri()) + "/auth";
|
||||
return KeycloakUriBuilder.fromUri(authServerBaseUrl).path(ServiceUrlConstants.REALM_INFO_PATH).build(REALM_NAME).toString();
|
||||
}
|
||||
|
||||
private AuthorizationEndpointRequestObject createValidRequestObjectForSecureRequestObjectExecutor(String clientId) throws URISyntaxException {
|
||||
AuthorizationEndpointRequestObject requestObject = new AuthorizationEndpointRequestObject();
|
||||
requestObject.id(KeycloakModelUtils.generateId());
|
||||
|
|
Loading…
Reference in a new issue