Issue #9194: Client authentication fails when using signed JWT, if the JWA signing algorithm is not RS256
This commit is contained in:
parent
1915f11cba
commit
5332a7d435
15 changed files with 232 additions and 26 deletions
|
@ -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) {
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
Loading…
Reference in a new issue