Issue #9194: Client authentication fails when using signed JWT, if the JWA signing algorithm is not RS256

This commit is contained in:
rmartinc 2021-12-20 08:58:30 +01:00 committed by Marek Posolda
parent 1915f11cba
commit 5332a7d435
15 changed files with 232 additions and 26 deletions

View file

@ -24,12 +24,18 @@ import java.util.Map;
import org.keycloak.OAuth2Constants;
import org.keycloak.adapters.AdapterUtils;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.jose.jwk.JWK;
import org.keycloak.jose.jwk.JWKBuilder;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.representations.JsonWebToken;
import org.keycloak.common.util.KeystoreUtil;
import org.keycloak.common.util.Time;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.AsymmetricSignatureSignerContext;
import org.keycloak.crypto.JavaAlgorithm;
import org.keycloak.crypto.KeyType;
import org.keycloak.crypto.KeyUse;
import org.keycloak.crypto.KeyWrapper;
import org.keycloak.crypto.SignatureSignerContext;
/**
* Client authentication based on JWT signed by client private key .
@ -42,7 +48,7 @@ public class JWTClientCredentialsProvider implements ClientCredentialsProvider {
public static final String PROVIDER_ID = "jwt";
private KeyPair keyPair;
private JWK publicKeyJwk;
private SignatureSignerContext sigCtx;
private int tokenTimeout;
@ -52,8 +58,35 @@ public class JWTClientCredentialsProvider implements ClientCredentialsProvider {
}
public void setupKeyPair(KeyPair keyPair) {
setupKeyPair(keyPair, Algorithm.RS256);
}
public void setupKeyPair(KeyPair keyPair, String algorithm) {
// check the algorithm is valid
switch (keyPair.getPublic().getAlgorithm()) {
case KeyType.RSA:
if (!JavaAlgorithm.isRSAJavaAlgorithm(algorithm)) {
throw new RuntimeException("Invalid algorithm for a RSA KeyPair: " + algorithm);
}
break;
case KeyType.EC:
if (!JavaAlgorithm.isECJavaAlgorithm(algorithm)) {
throw new RuntimeException("Invalid algorithm for a EC KeyPair: " + algorithm);
}
break;
default:
throw new RuntimeException("Invalid KeyPair algorithm: " + keyPair.getPublic().getAlgorithm());
}
// create the key and signature context
KeyWrapper keyWrapper = new KeyWrapper();
keyWrapper.setKid(KeyUtils.createKeyId(keyPair.getPublic()));
keyWrapper.setAlgorithm(algorithm);
keyWrapper.setPrivateKey(keyPair.getPrivate());
keyWrapper.setPublicKey(keyPair.getPublic());
keyWrapper.setType(keyPair.getPublic().getAlgorithm());
keyWrapper.setUse(KeyUse.SIG);
this.keyPair = keyPair;
this.publicKeyJwk = JWKBuilder.create().rs256(keyPair.getPublic());
this.sigCtx = new AsymmetricSignatureSignerContext(keyWrapper);
}
public void setTokenTimeout(int tokenTimeout) {
@ -99,8 +132,10 @@ public class JWTClientCredentialsProvider implements ClientCredentialsProvider {
clientKeyAlias = deployment.getResourceName();
}
String algorithm = (String) cfg.getOrDefault("algorithm", Algorithm.RS256);
KeyPair keyPair = KeystoreUtil.loadKeyPairFromKeystore(clientKeystoreFile, clientKeystorePassword, clientKeyPassword, clientKeyAlias, clientKeystoreFormat);
setupKeyPair(keyPair);
setupKeyPair(keyPair, algorithm);
this.tokenTimeout = asInt(cfg, "token-timeout", 10);
}
@ -132,9 +167,8 @@ public class JWTClientCredentialsProvider implements ClientCredentialsProvider {
public String createSignedRequestToken(String clientId, String realmInfoUrl) {
JsonWebToken jwt = createRequestToken(clientId, realmInfoUrl);
return new JWSBuilder()
.kid(publicKeyJwk.getKeyId())
.jsonContent(jwt)
.rsa256(keyPair.getPrivate());
.sign(sigCtx);
}
protected JsonWebToken createRequestToken(String clientId, String realmInfoUrl) {

View file

@ -103,4 +103,15 @@ public class JavaAlgorithm {
}
}
public static boolean isRSAJavaAlgorithm(String algorithm) {
return getJavaAlgorithm(algorithm).contains("RSA");
}
public static boolean isECJavaAlgorithm(String algorithm) {
return getJavaAlgorithm(algorithm).contains("ECDSA");
}
public static boolean isHMACJavaAlgorithm(String algorithm) {
return getJavaAlgorithm(algorithm).contains("HMAC");
}
}

View file

@ -21,6 +21,7 @@ import java.util.List;
import javax.crypto.SecretKey;
import java.security.Key;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Map;
public class KeyWrapper {
@ -166,4 +167,22 @@ public class KeyWrapper {
this.certificateChain = certificateChain;
}
public KeyWrapper cloneKey() {
KeyWrapper key = new KeyWrapper();
key.providerId = this.providerId;
key.providerPriority = this.providerPriority;
key.kid = this.kid;
key.algorithm = this.algorithm;
key.type = this.type;
key.use = this.use;
key.status = this.status;
key.secretKey = this.secretKey;
key.publicKey = this.publicKey;
key.privateKey = this.privateKey;
key.certificate = this.certificate;
if (this.certificateChain != null) {
key.certificateChain = new ArrayList<>(this.certificateChain);
}
return key;
}
}

View file

@ -142,7 +142,8 @@ public class InfinispanPublicKeyStorageProvider implements PublicKeyStorageProvi
if (entry != null) {
KeyWrapper publicKey = algorithm != null ? getPublicKeyByAlg(entry.getCurrentKeys(), algorithm) : getPublicKey(entry.getCurrentKeys(), kid);
if (publicKey != null) {
return publicKey;
// return a copy of the key to not modify the cached one
return publicKey.cloneKey();
}
}
@ -168,7 +169,8 @@ public class InfinispanPublicKeyStorageProvider implements PublicKeyStorageProvi
// Computation finished. Let's see if key is available
KeyWrapper publicKey = algorithm != null ? getPublicKeyByAlg(entry.getCurrentKeys(), algorithm) : getPublicKey(entry.getCurrentKeys(), kid);
if (publicKey != null) {
return publicKey;
// return a copy of the key to not modify the cached one
return publicKey.cloneKey();
}
} catch (ExecutionException ee) {

View file

@ -45,6 +45,7 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.SingleUseObjectProvider;
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.protocol.oidc.grants.ciba.CibaGrantType;
@ -258,6 +259,10 @@ public class JWTClientAuthenticator extends AbstractClientAuthenticator {
props.put("client-key-password", "REPLACE WITH THE KEY PASSWORD IN KEYSTORE");
props.put("client-key-alias", client.getClientId());
props.put("token-timeout", 10);
String algorithm = client.getAttribute(OIDCConfigAttributes.TOKEN_ENDPOINT_AUTH_SIGNING_ALG);
if (algorithm != null) {
props.put("algorithm", algorithm);
}
Map<String, Object> config = new HashMap<>();
config.put("jwt", props);

View file

@ -42,6 +42,7 @@ import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.models.ClientModel;
import org.keycloak.models.RealmModel;
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.JsonWebToken;
import org.keycloak.services.ServicesLogger;
@ -232,7 +233,10 @@ public class JWTClientSecretAuthenticator extends AbstractClientAuthenticator {
// }
Map<String, Object> props = new HashMap<>();
props.put("secret", client.getSecret());
// "algorithm" field is not saved because keycloak does not manage client's property of which algorithm is used for client secret signed JWT.
String algorithm = client.getAttribute(OIDCConfigAttributes.TOKEN_ENDPOINT_AUTH_SIGNING_ALG);
if (algorithm != null) {
props.put("algorithm", algorithm);
}
Map<String, Object> config = new HashMap<>();
config.put("secret-jwt", props);

View file

@ -33,11 +33,17 @@ public class ClientAsymmetricSignatureVerifierContext extends AsymmetricSignatur
if (key == null) {
throw new VerificationException("Key not found");
}
if (!KeyType.RSA.equals(key.getType())) {
throw new VerificationException("Key Type is not RSA: " + key.getType());
}
if (key.getAlgorithm() == null) {
// defaults to the algorithm set to the JWS
// validations should be performed prior to verifying signature in case there are restrictions on the algorithms
// that can used for signing
key.setAlgorithm(input.getHeader().getRawAlgorithm());
} else if (!key.getAlgorithm().equals(input.getHeader().getRawAlgorithm())) {
throw new VerificationException("Key Algorithms are different, key-algorithm=" + key.getAlgorithm()
+ " jwt-algorithm=" + input.getHeader().getRawAlgorithm());
}
return key;
}

View file

@ -16,6 +16,18 @@ public class ClientECDSASignatureVerifierContext extends AsymmetricSignatureVeri
if (key == null) {
throw new VerificationException("Key not found");
}
if (!KeyType.EC.equals(key.getType())) {
throw new VerificationException("Key Type is not EC: " + key.getType());
}
if (key.getAlgorithm() == null) {
// defaults to the algorithm set to the JWS
// validations should be performed prior to verifying signature in case there are restrictions on the algorithms
// that can used for signing
key.setAlgorithm(input.getHeader().getRawAlgorithm());
} else if (!key.getAlgorithm().equals(input.getHeader().getRawAlgorithm())) {
throw new VerificationException("Key Algorithms are different, key-algorithm=" + key.getAlgorithm()
+ " jwt-algorithm=" + input.getHeader().getRawAlgorithm());
}
return key;
}

View file

@ -20,8 +20,6 @@ package org.keycloak.keys.loader;
import org.jboss.logging.Logger;
import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
import org.keycloak.common.util.KeyUtils;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyType;
import org.keycloak.crypto.KeyUse;
import org.keycloak.crypto.KeyWrapper;
import org.keycloak.jose.jwk.JSONWebKeySet;
@ -106,8 +104,6 @@ public class ClientPublicKeyLoader implements PublicKeyLoader {
throw new ModelException("Client has both publicKey and certificate configured");
}
keyWrapper.setAlgorithm(Algorithm.RS256);
keyWrapper.setType(KeyType.RSA);
keyWrapper.setUse(KeyUse.SIG);
String kid = null;
if (encodedCertificate != null) {
@ -116,6 +112,7 @@ public class ClientPublicKeyLoader implements PublicKeyLoader {
kid = certInfo.getKid() != null ? certInfo.getKid() : KeyUtils.createKeyId(clientCert.getPublicKey());
keyWrapper.setKid(kid);
keyWrapper.setPublicKey(clientCert.getPublicKey());
keyWrapper.setType(clientCert.getPublicKey().getAlgorithm());
keyWrapper.setCertificate(clientCert);
} else {
PublicKey publicKey = KeycloakModelUtils.getPublicKey(encodedPublicKey);
@ -123,6 +120,7 @@ public class ClientPublicKeyLoader implements PublicKeyLoader {
kid = certInfo.getKid() != null ? certInfo.getKid() : KeyUtils.createKeyId(publicKey);
keyWrapper.setKid(kid);
keyWrapper.setPublicKey(publicKey);
keyWrapper.setType(publicKey.getAlgorithm());
}
return keyWrapper;
}

View file

@ -42,6 +42,7 @@ import org.keycloak.admin.client.resource.AuthorizationResource;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.ClientsResource;
import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
import org.keycloak.authentication.authenticators.client.JWTClientSecretAuthenticator;
import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.ClientAuthenticator;
import org.keycloak.authorization.client.Configuration;
@ -83,6 +84,20 @@ public class AuthzClientCredentialsTest extends AbstractAuthzTest {
.attribute(JWTClientAuthenticator.CERTIFICATE_ATTR, "MIICnTCCAYUCBgFPPLDaTzANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdjbGllbnQxMB4XDTE1MDgxNzE3MjI0N1oXDTI1MDgxNzE3MjQyN1owEjEQMA4GA1UEAwwHY2xpZW50MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIUjjgv+V3s96O+Za9002Lp/trtGuHBeaeVL9dFKMKzO2MPqdRmHB4PqNlDdd28Rwf5Xn6iWdFpyUKOnI/yXDLhdcuFpR0sMNK/C9Lt+hSpPFLuzDqgtPgDotlMxiHIWDOZ7g9/gPYNXbNvjv8nSiyqoguoCQiiafW90bPHsiVLdP7ZIUwCcfi1qQm7FhxRJ1NiW5dvUkuCnnWEf0XR+Wzc5eC9EgB0taLFiPsSEIlWMm5xlahYyXkPdNOqZjiRnrTWm5Y4uk8ZcsD/KbPTf/7t7cQXipVaswgjdYi1kK2/zRwOhg1QwWFX/qmvdd+fLxV0R6VqRDhn7Qep2cxwMxLsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAKE6OA46sf20bz8LZPoiNsqRwBUDkaMGXfnob7s/hJZIIwDEx0IAQ3uKsG7q9wb+aA6s+v7S340zb2k3IxuhFaHaZpAd4CyR5cn1FHylbzoZ7rI/3ASqHDqpljdJaFqPH+m7nZWtyDvtZf+gkZ8OjsndwsSBK1d/jMZPp29qYbl1+XfO7RCp/jDqro/R3saYFaIFiEZPeKn1hUJn6BO48vxH1xspSu9FmlvDOEAOz4AuM58z4zRMP49GcFdCWr1wkonJUHaSptJaQwmBwLFUkCbE5I1ixGMb7mjEud6Y5jhfzJiZMo2U8RfcjNbrN0diZl3jB6LQIwESnhYSghaTjNQ==")
.authenticatorType(JWTClientAuthenticator.PROVIDER_ID))
.build());
testRealms.add(configureRealm(RealmBuilder.create().name("authz-client-jwt-test-rs512"), ClientBuilder.create()
.attribute(JWTClientAuthenticator.CERTIFICATE_ATTR, "MIICnTCCAYUCBgFPPLDaTzANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdjbGllbnQxMB4XDTE1MDgxNzE3MjI0N1oXDTI1MDgxNzE3MjQyN1owEjEQMA4GA1UEAwwHY2xpZW50MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIUjjgv+V3s96O+Za9002Lp/trtGuHBeaeVL9dFKMKzO2MPqdRmHB4PqNlDdd28Rwf5Xn6iWdFpyUKOnI/yXDLhdcuFpR0sMNK/C9Lt+hSpPFLuzDqgtPgDotlMxiHIWDOZ7g9/gPYNXbNvjv8nSiyqoguoCQiiafW90bPHsiVLdP7ZIUwCcfi1qQm7FhxRJ1NiW5dvUkuCnnWEf0XR+Wzc5eC9EgB0taLFiPsSEIlWMm5xlahYyXkPdNOqZjiRnrTWm5Y4uk8ZcsD/KbPTf/7t7cQXipVaswgjdYi1kK2/zRwOhg1QwWFX/qmvdd+fLxV0R6VqRDhn7Qep2cxwMxLsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAKE6OA46sf20bz8LZPoiNsqRwBUDkaMGXfnob7s/hJZIIwDEx0IAQ3uKsG7q9wb+aA6s+v7S340zb2k3IxuhFaHaZpAd4CyR5cn1FHylbzoZ7rI/3ASqHDqpljdJaFqPH+m7nZWtyDvtZf+gkZ8OjsndwsSBK1d/jMZPp29qYbl1+XfO7RCp/jDqro/R3saYFaIFiEZPeKn1hUJn6BO48vxH1xspSu9FmlvDOEAOz4AuM58z4zRMP49GcFdCWr1wkonJUHaSptJaQwmBwLFUkCbE5I1ixGMb7mjEud6Y5jhfzJiZMo2U8RfcjNbrN0diZl3jB6LQIwESnhYSghaTjNQ==")
.attribute(OIDCConfigAttributes.TOKEN_ENDPOINT_AUTH_SIGNING_ALG, "RS512")
.authenticatorType(JWTClientAuthenticator.PROVIDER_ID))
.build());
testRealms.add(configureRealm(RealmBuilder.create().name("authz-client-jwt-test-es512"), ClientBuilder.create()
.attribute(JWTClientAuthenticator.CERTIFICATE_ATTR, "MIIBwjCCASKgAwIBAgIERlzM0jAMBggqhkjOPQQDBAUAMBIxEDAOBgNVBAMTB2NsaWVudDEwHhcNMjExMjE4MTAwMDQ2WhcNNDkwNTA1MTAwMDQ2WjASMRAwDgYDVQQDEwdjbGllbnQxMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBhg6xxAxP9ahWYpEtI0zaimLpwWIdiHuVSy6Eavg5Sljyr/xOBh34jli1SbOIYd/2EqtYeY8gX2SKkVE3MKc75rYBzqYYJrlYgO7NQyVpJ1JpFXeWqnBxTRwrSvRXSmx5BpssODKoIZGfhsiYpSJMuVK7FQ4ZX7+Fp5HG+yo6rCIxSKijITAfMB0GA1UdDgQWBBTr3aWlNiVniOPf3W435tybEvcL/jAMBggqhkjOPQQDBAUAA4GLADCBhwJBKO5yryGgOcW/dH980c9VeCHBho5ZH/zD+lsAS9CDxWrD3+QUMptf7Nfj7G6F0F1QARXK4bNUQ9ZW3kVzEsdvL9kCQgHjKvdLXNCDhk+J3b2nRrh30QztD0j2tpK8bvmO2kPz5DQ80tS8ICZv/LcZl5wnjBCavWn7POhzzmAG/UGkNSyZqQ==")
.authenticatorType(JWTClientAuthenticator.PROVIDER_ID))
.build());
testRealms.add(configureRealm(RealmBuilder.create().name("authz-client-jwt-test-hs512"), ClientBuilder.create()
.secret("weird-secret-for-test-hs512")
.attribute(OIDCConfigAttributes.TOKEN_ENDPOINT_AUTH_SIGNING_ALG, "HS512")
.authenticatorType(JWTClientSecretAuthenticator.PROVIDER_ID))
.build());
testRealms.add(configureRealm(RealmBuilder.create().name("authz-test"), ClientBuilder.create().secret("secret")).build());
testRealms.add(configureRealm(RealmBuilder.create().name("authz-test-session").accessTokenLifespan(1), ClientBuilder.create().secret("secret")).build());
testRealms.add(configureRealm(RealmBuilder.create().name("authz-test-no-rt").accessTokenLifespan(1), ClientBuilder.create().secret("secret").attribute(OIDCConfigAttributes.USE_REFRESH_TOKEN_FOR_CLIENT_CREDENTIALS_GRANT, "false")).build());
@ -119,9 +134,8 @@ public class AuthzClientCredentialsTest extends AbstractAuthzTest {
assertAccessProtectionAPI(getAuthzClient("keycloak-with-jwt-authentication.json").protection());
}
@Test
public void testSuccessfulAuthorizationRequest() throws Exception {
AuthzClient authzClient = getAuthzClient("keycloak-with-jwt-authentication.json");
private void testSuccessfulAuthorizationRequest(String config) throws Exception {
AuthzClient authzClient = getAuthzClient(config);
ProtectionResource protection = authzClient.protection();
PermissionRequest request = new PermissionRequest("Default Resource");
PermissionResponse ticketResponse = protection.permission().create(request);
@ -144,6 +158,26 @@ public class AuthzClientCredentialsTest extends AbstractAuthzTest {
assertEquals("Default Resource", permissions.get(0).getResourceName());
}
@Test
public void testSuccessfulAuthorizationRS256Request() throws Exception {
testSuccessfulAuthorizationRequest("keycloak-with-jwt-authentication.json");
}
@Test
public void testSuccessfulAuthorizationRS512Request() throws Exception {
testSuccessfulAuthorizationRequest("keycloak-with-jwt-rs512-authentication.json");
}
@Test
public void testSuccessfulAuthorizationHS512Request() throws Exception {
testSuccessfulAuthorizationRequest("keycloak-with-jwt-hs512-authentication.json");
}
@Test
public void testSuccessfulAuthorizationES512Request() throws Exception {
testSuccessfulAuthorizationRequest("keycloak-with-jwt-es512-authentication.json");
}
@Test
public void failJWTAuthentication() {
try {

View file

@ -278,8 +278,7 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest {
}
@Test
public void testCodeToTokenRequestSuccess() throws Exception {
public void testCodeToTokenRequestSuccess(String algorithm) throws Exception {
oauth.clientId("client2");
oauth.doLogin("test-user@localhost", "password");
EventRepresentation loginEvent = events.expectLogin()
@ -287,7 +286,7 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest {
.assertEvent();
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
OAuthClient.AccessTokenResponse response = doAccessTokenRequest(code, getClient2SignedJWT());
OAuthClient.AccessTokenResponse response = doAccessTokenRequest(code, getClient2SignedJWT(algorithm));
assertEquals(200, response.getStatusCode());
oauth.verifyToken(response.getAccessToken());
@ -298,6 +297,37 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest {
.assertEvent();
}
public void testCodeToTokenRequestSuccessForceAlgInClient(String algorithm) throws Exception {
ClientManager.realm(adminClient.realm("test")).clientId("client2")
.updateAttribute(OIDCConfigAttributes.TOKEN_ENDPOINT_AUTH_SIGNING_ALG, algorithm);
try {
testCodeToTokenRequestSuccess(algorithm);
} finally {
ClientManager.realm(adminClient.realm("test")).clientId("client2")
.updateAttribute(OIDCConfigAttributes.TOKEN_ENDPOINT_AUTH_SIGNING_ALG, null);
}
}
@Test
public void testCodeToTokenRequestSuccess() throws Exception {
testCodeToTokenRequestSuccess(Algorithm.RS256);
}
@Test
public void testCodeToTokenRequestSuccess512() throws Exception {
testCodeToTokenRequestSuccess(Algorithm.RS512);
}
@Test
public void testCodeToTokenRequestSuccessPS384() throws Exception {
testCodeToTokenRequestSuccessForceAlgInClient(Algorithm.PS384);
}
@Test
public void testCodeToTokenRequestSuccessPS512() throws Exception {
testCodeToTokenRequestSuccessForceAlgInClient(Algorithm.PS512);
}
@Test
public void testCodeToTokenRequestSuccessES256usingJwksUri() throws Exception {
testCodeToTokenRequestSuccess(Algorithm.ES256, true);
@ -494,14 +524,14 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest {
OAuthClient.AccessTokenResponse response = doGrantAccessTokenRequest("test-user@localhost", "password", createSignedRequestToken("client2", getRealmInfoUrl(), privateKey, publicKey, signingAlgorithm));
assertEquals(200, response.getStatusCode());
// send a JWS using a algorithm other than the default (RS256)
// sending a JWS using another RSA based alg (PS256) should work as alg is not specified
publicKey = keyPair.getPublic();
privateKey = keyPair.getPrivate();
oauth.clientId("client2");
response = doGrantAccessTokenRequest("test-user@localhost", "password", createSignedRequestToken("client2", getRealmInfoUrl(), privateKey, publicKey, Algorithm.PS256));
assertEquals(400, response.getStatusCode());
assertEquals("Client authentication with signed JWT failed: Signature on JWT token failed validation", response.getErrorDescription());
assertEquals(200, response.getStatusCode());
// sending an invalid EC (ES256) one should not work
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRepresentation).setTokenEndpointAuthSigningAlg(Algorithm.ES256);
clientResource.update(clientRepresentation);
response = doGrantAccessTokenRequest("test-user@localhost", "password", createSignedRequestToken("client2", getRealmInfoUrl(), privateKey, publicKey, Algorithm.PS256));
@ -1261,12 +1291,16 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest {
}
}
private String getClient2SignedJWT(String algorithm) {
return getClientSignedJWT(getClient2KeyPair(), "client2", algorithm);
}
private String getClient1SignedJWT() {
return getClientSignedJWT(getClient1KeyPair(), "client1");
return getClientSignedJWT(getClient1KeyPair(), "client1", Algorithm.RS256);
}
private String getClient2SignedJWT() {
return getClientSignedJWT(getClient2KeyPair(), "client2");
return getClientSignedJWT(getClient2KeyPair(), "client2", Algorithm.RS256);
}
private KeyPair getClient1KeyPair() {
@ -1280,8 +1314,12 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest {
}
private String getClientSignedJWT(KeyPair keyPair, String clientId) {
return getClientSignedJWT(keyPair, clientId, Algorithm.RS256);
}
private String getClientSignedJWT(KeyPair keyPair, String clientId, String algorithm) {
JWTClientCredentialsProvider jwtProvider = new JWTClientCredentialsProvider();
jwtProvider.setupKeyPair(keyPair);
jwtProvider.setupKeyPair(keyPair, algorithm);
jwtProvider.setTokenTimeout(10);
return jwtProvider.createSignedRequestToken(clientId, getRealmInfoUrl());
}

View file

@ -0,0 +1,16 @@
{
"realm": "authz-client-jwt-test-es512",
"auth-server-url" : "http://localhost:8180/auth",
"resource" : "resource-server-test",
"credentials": {
"jwt": {
"client-keystore-file": "classpath:client-auth-test/keystore-client3.jks",
"client-keystore-type": "JKS",
"client-keystore-password": "storepass",
"client-key-alias": "clientkey",
"client-key-password": "keypass",
"algorithm": "ES512",
"token-expiration": 10
}
}
}

View file

@ -0,0 +1,11 @@
{
"realm": "authz-client-jwt-test-hs512",
"auth-server-url" : "http://localhost:8180/auth",
"resource" : "resource-server-test",
"credentials": {
"secret-jwt": {
"secret": "weird-secret-for-test-hs512",
"algorithm": "HS512"
}
}
}

View file

@ -0,0 +1,16 @@
{
"realm": "authz-client-jwt-test-rs512",
"auth-server-url" : "http://localhost:8180/auth",
"resource" : "resource-server-test",
"credentials": {
"jwt": {
"client-keystore-file": "classpath:client-auth-test/keystore-client1.jks",
"client-keystore-type": "JKS",
"client-keystore-password": "storepass",
"client-key-alias": "clientkey",
"client-key-password": "keypass",
"algorithm": "RS512",
"token-expiration": 10
}
}
}