diff --git a/http/oidc/pom.xml b/http/oidc/pom.xml
index b01609b09f0..ecc11a62668 100644
--- a/http/oidc/pom.xml
+++ b/http/oidc/pom.xml
@@ -128,6 +128,11 @@
keycloak-admin-client
test
+
+ org.keycloak
+ keycloak-services
+ test
+
org.jboss.logmanager
jboss-logmanager
@@ -173,6 +178,17 @@
jmockit
test
+
+ org.wildfly.security
+ wildfly-elytron-credential-source-impl
+ test
+
+
+ org.wildfly.security
+ wildfly-elytron-tests-common
+ test-jar
+ test
+
diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/ElytronMessages.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/ElytronMessages.java
index c4ba08c8fb2..5726e4e7665 100644
--- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/ElytronMessages.java
+++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/ElytronMessages.java
@@ -19,6 +19,7 @@
package org.wildfly.security.http.oidc;
import static org.jboss.logging.Logger.Level.ERROR;
+import static org.jboss.logging.Logger.Level.INFO;
import static org.jboss.logging.Logger.Level.WARN;
import static org.jboss.logging.annotations.Message.NONE;
@@ -233,5 +234,20 @@ interface ElytronMessages extends BasicLogger {
@Message(id = 23056, value = "No message entity")
IOException noMessageEntity();
+ @Message(id = 23057, value = "Invalid keystore configuration for signing Request Objects.")
+ IOException invalidKeyStoreConfiguration();
+
+ @Message(id = 23058, value = "The signature algorithm specified is not supported by the OpenID Provider.")
+ IOException invalidRequestObjectSignatureAlgorithm();
+
+ @Message(id = 23059, value = "The encryption algorithm specified is not supported by the OpenID Provider.")
+ IOException invalidRequestObjectEncryptionAlgorithm();
+
+ @Message(id = 23060, value = "The content encryption algorithm specified is not supported by the OpenID Provider.")
+ IOException invalidRequestObjectContentEncryptionAlgorithm();
+
+ @LogMessage(level = INFO)
+ @Message(id = 23061, value = "The OpenID provider does not support request parameters. Sending the request using OAuth2 format.")
+ void requestParameterNotSupported();
}
diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/JWKPublicKeySetExtractor.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/JWKPublicKeySetExtractor.java
new file mode 100644
index 00000000000..4069b73b7bb
--- /dev/null
+++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/JWKPublicKeySetExtractor.java
@@ -0,0 +1,44 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2020 Red Hat, Inc., and individual 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.wildfly.security.http.oidc;
+
+import static org.apache.http.HttpHeaders.ACCEPT;
+import static org.wildfly.security.http.oidc.Oidc.JSON_CONTENT_TYPE;
+
+import java.io.IOException;
+import org.apache.http.client.methods.HttpGet;
+import org.jose4j.lang.JoseException;
+import org.wildfly.security.jose.jwk.JsonWebKeySet;
+/**
+ * A public key locator that dynamically obtains the public key from an OpenID
+ * provider by sending a request to the provider's {@code jwks_uri} when needed.
+ *
+ * @author Prarthona Paul
+ * */
+public class JWKPublicKeySetExtractor implements OidcPublicKeyExtractor {
+ public JWKPublicKeySetExtractor() {
+ }
+ @Override
+ public JsonWebKeySet extractPublicKeySet(OidcClientConfiguration config) throws IOException, JoseException {
+ HttpGet request = new HttpGet(config.getJwksUrl());
+ request.addHeader(ACCEPT, JSON_CONTENT_TYPE);
+ return Oidc.sendJsonHttpRequest(config, request, JsonWebKeySet.class);
+ }
+
+}
diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/JWTClientCredentialsProvider.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/JWTClientCredentialsProvider.java
index 4da8d3a5384..5ea6383e7a5 100644
--- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/JWTClientCredentialsProvider.java
+++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/JWTClientCredentialsProvider.java
@@ -22,15 +22,9 @@
import static org.wildfly.security.http.oidc.Oidc.CLIENT_ASSERTION;
import static org.wildfly.security.http.oidc.Oidc.CLIENT_ASSERTION_TYPE;
import static org.wildfly.security.http.oidc.Oidc.CLIENT_ASSERTION_TYPE_JWT;
-import static org.wildfly.security.http.oidc.Oidc.PROTOCOL_CLASSPATH;
import static org.wildfly.security.http.oidc.Oidc.asInt;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.InputStream;
import java.security.KeyPair;
-import java.security.KeyStore;
-import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Map;
@@ -115,7 +109,7 @@ public void init(OidcClientConfiguration oidcClientConfiguration, Object credent
clientKeyAlias = oidcClientConfiguration.getResourceName();
}
- KeyPair keyPair = loadKeyPairFromKeyStore(clientKeyStoreFile, clientKeyStorePassword, clientKeyPassword, clientKeyAlias, clientKeyStoreType);
+ KeyPair keyPair = new JWTSigningUtils().loadKeyPairFromKeyStore(clientKeyStoreFile, clientKeyStorePassword, clientKeyPassword, clientKeyAlias, clientKeyStoreType);
setupKeyPair(keyPair);
this.tokenTimeout = asInt(cfg, "token-timeout", 10);
}
@@ -155,43 +149,4 @@ protected JwtClaims createRequestToken(String clientId, String tokenUrl) {
jwtClaims.setExpirationTime(exp);
return jwtClaims;
}
-
- private static KeyPair loadKeyPairFromKeyStore(String keyStoreFile, String storePassword, String keyPassword, String keyAlias, String keyStoreType) {
- InputStream stream = findFile(keyStoreFile);
- try {
- KeyStore keyStore = KeyStore.getInstance(keyStoreType);
- keyStore.load(stream, storePassword.toCharArray());
- PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias, keyPassword.toCharArray());
- if (privateKey == null) {
- log.unableToLoadKeyWithAlias(keyAlias);
- }
- PublicKey publicKey = keyStore.getCertificate(keyAlias).getPublicKey();
- return new KeyPair(publicKey, privateKey);
- } catch (Exception e) {
- throw log.unableToLoadPrivateKey(e);
- }
- }
-
- private static InputStream findFile(String keystoreFile) {
- if (keystoreFile.startsWith(PROTOCOL_CLASSPATH)) {
- String classPathLocation = keystoreFile.replace(PROTOCOL_CLASSPATH, "");
- // try current class classloader first
- InputStream is = JWTClientCredentialsProvider.class.getClassLoader().getResourceAsStream(classPathLocation);
- if (is == null) {
- is = Thread.currentThread().getContextClassLoader().getResourceAsStream(classPathLocation);
- }
- if (is != null) {
- return is;
- } else {
- throw log.unableToFindKeystoreFile(keystoreFile);
- }
- } else {
- try {
- // fallback to file
- return new FileInputStream(keystoreFile);
- } catch (FileNotFoundException e) {
- throw new RuntimeException(e);
- }
- }
- }
}
diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/JWTSigning.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/JWTSigning.java
new file mode 100644
index 00000000000..0b9207934c2
--- /dev/null
+++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/JWTSigning.java
@@ -0,0 +1,46 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2023 Red Hat, Inc., and individual 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.wildfly.security.http.oidc;
+
+import java.io.InputStream;
+import java.security.KeyPair;
+
+/**
+ * An interface to obtain the KeyPair from a keystore file.
+ *
+ * @author Prarthona Paul
+ */
+
+public interface JWTSigning {
+ /**
+ * @param keyStoreFile the path to the keystore file
+ * @param storePassword the password for the keystore file
+ * @param keyPassword the password for the key we would like ot extract from the keystore
+ * @param keyAlias the alias for the key that uniquely identifies it
+ * @param keyStoreType the type of keystore we are trying to access
+ * @return the private-public keypair extracted from the keystore
+ */
+ KeyPair loadKeyPairFromKeyStore(String keyStoreFile, String storePassword, String keyPassword, String keyAlias, String keyStoreType);
+
+ /**
+ * @param keystoreFile the path the keystore file we are trying to access
+ * @return the contents of the file as an inputStream
+ */
+ InputStream findFile(String keystoreFile);
+}
diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/JWTSigningUtils.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/JWTSigningUtils.java
new file mode 100644
index 00000000000..4725e0d5e80
--- /dev/null
+++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/JWTSigningUtils.java
@@ -0,0 +1,81 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2020 Red Hat, Inc., and individual 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.wildfly.security.http.oidc;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.security.KeyPair;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+
+import static org.wildfly.security.http.oidc.ElytronMessages.log;
+import static org.wildfly.security.http.oidc.Oidc.PROTOCOL_CLASSPATH;
+
+/**
+ * An interface to obtain the KeyPair from a keystore file.
+ *
+ * @author Prarthona Paul
+ */
+
+public class JWTSigningUtils implements JWTSigning{
+ public JWTSigningUtils() {}
+
+ @Override
+ public KeyPair loadKeyPairFromKeyStore(String keyStoreFile, String storePassword, String keyPassword, String keyAlias, String keyStoreType) {
+ InputStream stream = findFile(keyStoreFile);
+ try {
+ KeyStore keyStore = KeyStore.getInstance(keyStoreType);
+ keyStore.load(stream, storePassword.toCharArray());
+ PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias, keyPassword.toCharArray());
+ if (privateKey == null) {
+ log.unableToLoadKeyWithAlias(keyAlias);
+ }
+ PublicKey publicKey = keyStore.getCertificate(keyAlias).getPublicKey();
+ return new KeyPair(publicKey, privateKey);
+ } catch (Exception e) {
+ throw log.unableToLoadPrivateKey(e);
+ }
+ }
+
+ @Override
+ public InputStream findFile(String keystoreFile) {
+ if (keystoreFile.startsWith(PROTOCOL_CLASSPATH)) {
+ String classPathLocation = keystoreFile.replace(PROTOCOL_CLASSPATH, "");
+ // try current class classloader first
+ InputStream is = JWTClientCredentialsProvider.class.getClassLoader().getResourceAsStream(classPathLocation);
+ if (is == null) {
+ is = Thread.currentThread().getContextClassLoader().getResourceAsStream(classPathLocation);
+ }
+ if (is != null) {
+ return is;
+ } else {
+ throw log.unableToFindKeystoreFile(keystoreFile);
+ }
+ } else {
+ try {
+ // fallback to file
+ return new FileInputStream(keystoreFile);
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+}
diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/Oidc.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/Oidc.java
index 8d0170fa75a..ee1030b9042 100644
--- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/Oidc.java
+++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/Oidc.java
@@ -73,6 +73,8 @@ public class Oidc {
public static final String PARTIAL = "partial/";
public static final String PASSWORD = "password";
public static final String PROMPT = "prompt";
+ public static final String REQUEST = "request";
+ public static final String REQUEST_URI = "request_uri";
public static final String SCOPE = "scope";
public static final String UI_LOCALES = "ui_locales";
public static final String USERNAME = "username";
@@ -199,6 +201,27 @@ public enum TokenStore {
COOKIE
}
+ public enum AuthenticationFormat {
+ REQUEST_TYPE_OAUTH2("oauth2"),
+ REQUEST_TYPE_REQUEST("request"),
+ REQUEST_TYPE_REQUEST_URI("request_uri");
+
+ private final String value;
+
+ AuthenticationFormat(String value) {
+ this.value = value;
+ }
+
+ /**
+ * Get the string value for this referral mode.
+ *
+ * @return the string value for this referral mode
+ */
+ public String getValue() {
+ return value;
+ }
+ }
+
public enum ClientCredentialsProviderType {
SECRET("secret"),
JWT("jwt"),
diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientConfiguration.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientConfiguration.java
index 3e18fb4eb6d..5a1324137b9 100644
--- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientConfiguration.java
+++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientConfiguration.java
@@ -30,9 +30,11 @@
import static org.wildfly.security.http.oidc.Oidc.SLASH;
import static org.wildfly.security.http.oidc.Oidc.SSLRequired;
import static org.wildfly.security.http.oidc.Oidc.TokenStore;
+import static org.wildfly.security.jose.util.JsonSerialization.readValue;
import java.net.URI;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
@@ -41,7 +43,6 @@
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.util.EntityUtils;
-import org.wildfly.security.jose.util.JsonSerialization;
/**
* The OpenID Connect (OIDC) configuration for a client application. This class is based on
@@ -81,6 +82,11 @@ public enum RelativeUrlsUsed {
protected String jwksUrl;
protected String issuerUrl;
protected String principalAttribute = "sub";
+ protected List requestObjectSigningAlgValuesSupported;
+ protected List requestObjectContentEncryptionMethodsSupported;
+ protected List requestObjectEncryptionAlgValuesSupported;
+ protected boolean requestParameterSupported;
+ protected boolean requestUriParameterSupported;
protected String resource;
protected String clientId;
@@ -126,6 +132,16 @@ public enum RelativeUrlsUsed {
protected boolean verifyTokenAudience = false;
protected String tokenSignatureAlgorithm = DEFAULT_TOKEN_SIGNATURE_ALGORITHM;
+ protected String authenticationRequestFormat;
+ protected String requestSignatureAlgorithm;
+ protected String requestEncryptAlgorithm;
+ protected String requestContentEncryptionMethod;
+ protected String pushedAuthorizationRequestEndpoint;
+ protected String requestObjectSigningKeystoreFile;
+ protected String requestObjectSigningKeyStorePass;
+ protected String requestObjectSigningKeyPass;
+ protected String requestObjectSigningKeyAlias;
+ protected String requestObjectSigningKeystoreType;
public OidcClientConfiguration() {
}
@@ -223,6 +239,13 @@ protected void resolveUrls() {
tokenUrl = config.getTokenEndpoint();
logoutUrl = config.getLogoutEndpoint();
jwksUrl = config.getJwksUri();
+ requestParameterSupported = config.getRequestParameterSupported();
+ requestObjectSigningAlgValuesSupported = config.getRequestObjectSigningAlgValuesSupported();
+ requestObjectContentEncryptionMethodsSupported = config.getRequestObjectContentEncryptionMethodsSupported();
+ requestObjectEncryptionAlgValuesSupported = config.getRequestObjectEncryptionAlgValuesSupported();
+ requestUriParameterSupported = config.getRequestUriParameterSupported();
+ pushedAuthorizationRequestEndpoint = config.getPushedAuthorizationRequestEndpoint();
+
if (authServerBaseUrl != null) {
// keycloak-specific properties
accountUrl = getUrl(issuerUrl, ACCOUNT_PATH);
@@ -246,7 +269,7 @@ protected OidcProviderMetadata getOidcProviderMetadata(String discoveryUrl) thro
EntityUtils.consumeQuietly(response.getEntity());
throw new Exception(response.getStatusLine().getReasonPhrase());
}
- return JsonSerialization.readValue(response.getEntity().getContent(), OidcProviderMetadata.class);
+ return readValue(response.getEntity().getContent(), OidcProviderMetadata.class);
} finally {
request.releaseConnection();
}
@@ -329,6 +352,26 @@ public String getIssuerUrl() {
return issuerUrl;
}
+ public List getRequestObjectSigningAlgValuesSupported() {
+ return requestObjectSigningAlgValuesSupported;
+ }
+
+ public List getRequestObjectEncryptionAlgValuesSupported() {
+ return requestObjectEncryptionAlgValuesSupported;
+ }
+
+ public List getRequestObjectContentEncryptionMethodsSupported() {
+ return requestObjectContentEncryptionMethodsSupported;
+ }
+
+ public boolean getRequestParameterSupported() {
+ return requestParameterSupported;
+ }
+
+ public boolean getRequestUriParameterSupported() {
+ return requestUriParameterSupported;
+ }
+
public void setResource(String resource) {
this.resource = resource;
}
@@ -648,4 +691,83 @@ public String getTokenSignatureAlgorithm() {
return tokenSignatureAlgorithm;
}
+ public String getAuthenticationRequestFormat() {
+ return authenticationRequestFormat;
+ }
+
+ public void setAuthenticationRequestFormat(String authenticationRequestFormat ) {
+ this.authenticationRequestFormat = authenticationRequestFormat;
+ }
+
+ public String getRequestSignatureAlgorithm() {
+ return requestSignatureAlgorithm;
+ }
+
+ public void setRequestSignatureAlgorithm(String requestSignatureAlgorithm) {
+ this.requestSignatureAlgorithm = requestSignatureAlgorithm;
+ }
+
+ public String getRequestEncryptAlgorithm() {
+ return requestEncryptAlgorithm;
+ }
+
+ public void setRequestEncryptAlgorithm(String requestEncryptAlgorithm) {
+ this.requestEncryptAlgorithm = requestEncryptAlgorithm;
+ }
+
+ public String getRequestContentEncryptionMethod() {
+ return requestContentEncryptionMethod;
+ }
+
+ public void setRequestContentEncryptionMethod(String requestContentEncryptionMethod) {
+ this.requestContentEncryptionMethod = requestContentEncryptionMethod;
+ }
+
+ public String getRequestObjectSigningKeystoreFile() {
+ return requestObjectSigningKeystoreFile;
+ }
+
+ public void setRequestObjectSigningKeystoreFile(String keyStoreFile) {
+ this.requestObjectSigningKeystoreFile = keyStoreFile;
+ }
+
+ public String getRequestObjectSigningKeyStorePassword() {
+ return requestObjectSigningKeyStorePass;
+ }
+
+ public void setRequestObjectSigningKeyStorePassword(String pass) {
+ this.requestObjectSigningKeyStorePass = pass;
+ }
+
+ public String getRequestObjectSigningKeyPassword() {
+ return requestObjectSigningKeyPass;
+ }
+
+ public void setRequestObjectSigningKeyPassword(String pass) {
+ this.requestObjectSigningKeyPass = pass;
+ }
+
+ public String getRequestObjectSigningKeystoreType() {
+ return requestObjectSigningKeystoreType;
+ }
+
+ public void setRequestObjectSigningKeystoreType(String type) {
+ this.requestObjectSigningKeystoreType = type;
+ }
+
+ public String getRequestObjectSigningKeyAlias() {
+ return requestObjectSigningKeyAlias;
+ }
+
+ public void setRequestObjectSigningKeyAlias(String alias) {
+ this.requestObjectSigningKeyAlias = alias;
+ }
+
+ public String getPushedAuthorizationRequestEndpoint() {
+ return pushedAuthorizationRequestEndpoint;
+ }
+
+ public void setPushedAuthorizationRequestEndpoint(String url) {
+ this.pushedAuthorizationRequestEndpoint = url;
+ }
}
diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientConfigurationBuilder.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientConfigurationBuilder.java
index 99f9b185a5d..ec886a17a8d 100644
--- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientConfigurationBuilder.java
+++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientConfigurationBuilder.java
@@ -19,6 +19,8 @@
package org.wildfly.security.http.oidc;
import static org.wildfly.security.http.oidc.ElytronMessages.log;
+import static org.jose4j.jws.AlgorithmIdentifiers.NONE;
+import static org.wildfly.security.http.oidc.Oidc.AuthenticationFormat.REQUEST_TYPE_OAUTH2;
import static org.wildfly.security.http.oidc.Oidc.SSLRequired;
import static org.wildfly.security.http.oidc.Oidc.TokenStore;
@@ -100,6 +102,32 @@ protected OidcClientConfiguration internalBuild(final OidcJsonConfiguration oidc
if (oidcJsonConfiguration.getTokenCookiePath() != null) {
oidcClientConfiguration.setOidcStateCookiePath(oidcJsonConfiguration.getTokenCookiePath());
}
+ if (oidcJsonConfiguration.getAuthenticationRequestFormat() != null) {
+ oidcClientConfiguration.setAuthenticationRequestFormat(oidcJsonConfiguration.getAuthenticationRequestFormat());
+ } else {
+ oidcClientConfiguration.setAuthenticationRequestFormat(REQUEST_TYPE_OAUTH2.getValue());
+ }
+ if (oidcJsonConfiguration.getRequestSignatureAlgorithm() != null) {
+ oidcClientConfiguration.setRequestSignatureAlgorithm(oidcJsonConfiguration.getRequestSignatureAlgorithm());
+ } else {
+ oidcClientConfiguration.setRequestSignatureAlgorithm(NONE);
+ }
+ if (oidcJsonConfiguration.getRequestEncryptAlgorithm() != null && oidcJsonConfiguration.getRequestContentEncryptionMethod() != null) { //both are required to encrypt the request object
+ oidcClientConfiguration.setRequestEncryptAlgorithm(oidcJsonConfiguration.getRequestEncryptAlgorithm());
+ oidcClientConfiguration.setRequestContentEncryptionMethod(oidcJsonConfiguration.getRequestContentEncryptionMethod());
+ }
+ if (oidcJsonConfiguration.getRequestObjectSigningKeyStoreFile() != null
+ && oidcJsonConfiguration.getRequestObjectSigningKeystorePassword() != null
+ && oidcJsonConfiguration.getRequestObjectSigningKeyPassword() != null
+ && oidcJsonConfiguration.getRequestObjectSigningKeyAlias() != null) {
+ oidcClientConfiguration.setRequestObjectSigningKeystoreFile(oidcJsonConfiguration.getRequestObjectSigningKeyStoreFile());
+ oidcClientConfiguration.setRequestObjectSigningKeyStorePassword(oidcJsonConfiguration.getRequestObjectSigningKeystorePassword());
+ oidcClientConfiguration.setRequestObjectSigningKeyPassword(oidcJsonConfiguration.getRequestObjectSigningKeyPassword());
+ oidcClientConfiguration.setRequestObjectSigningKeyAlias(oidcJsonConfiguration.getRequestObjectSigningKeyAlias());
+ if (oidcJsonConfiguration.getRequestObjectSigningKeystoreType() != null) {
+ oidcClientConfiguration.setRequestObjectSigningKeystoreType(oidcJsonConfiguration.getRequestObjectSigningKeystoreType());
+ }
+ }
if (oidcJsonConfiguration.getPrincipalAttribute() != null) oidcClientConfiguration.setPrincipalAttribute(oidcJsonConfiguration.getPrincipalAttribute());
oidcClientConfiguration.setResourceCredentials(oidcJsonConfiguration.getCredentials());
@@ -190,7 +218,6 @@ public static OidcJsonConfiguration loadOidcJsonConfiguration(InputStream is) {
return adapterConfig;
}
-
public static OidcClientConfiguration build(OidcJsonConfiguration oidcJsonConfiguration) {
return new OidcClientConfigurationBuilder().internalBuild(oidcJsonConfiguration);
}
diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientContext.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientContext.java
index 3c249bb846b..62424e7b5c3 100644
--- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientContext.java
+++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcClientContext.java
@@ -525,6 +525,107 @@ public String getTokenSignatureAlgorithm() {
public void setTokenSignatureAlgorithm(String tokenSignatureAlgorithm) {
delegate.setTokenSignatureAlgorithm(tokenSignatureAlgorithm);
}
+
+ @Override
+ public String getAuthenticationRequestFormat() {
+ return delegate.getAuthenticationRequestFormat();
+ }
+
+ @Override
+ public void setAuthenticationRequestFormat(String authFormat) {
+ delegate.setAuthenticationRequestFormat(authFormat);
+ }
+
+ @Override
+ public String getRequestSignatureAlgorithm() {
+ return delegate.getRequestSignatureAlgorithm();
+ }
+
+ @Override
+ public void setRequestSignatureAlgorithm(String requestSignature) {
+ delegate.setRequestSignatureAlgorithm(requestSignature);
+ }
+
+ @Override
+ public String getRequestEncryptAlgorithm() {
+ return delegate.getRequestEncryptAlgorithm();
+ }
+
+ @Override
+ public void setRequestEncryptAlgorithm(String algorithm) {
+ delegate.setRequestEncryptAlgorithm(algorithm);
+ }
+
+ @Override
+ public String getRequestContentEncryptionMethod() {
+ return delegate.requestContentEncryptionMethod;
+ }
+
+ @Override
+ public void setRequestContentEncryptionMethod (String enc) {
+ delegate.requestContentEncryptionMethod = enc;
+ }
+
+ @Override
+ public String getRequestObjectSigningKeystoreFile() {
+ return delegate.requestObjectSigningKeystoreFile;
+ }
+
+ @Override
+ public void setRequestObjectSigningKeystoreFile(String keyStoreFile) {
+ delegate.requestObjectSigningKeystoreFile = keyStoreFile;
+ }
+
+ @Override
+ public String getRequestObjectSigningKeyStorePassword() {
+ return delegate.requestObjectSigningKeyStorePass;
+ }
+
+ @Override
+ public void setRequestObjectSigningKeyStorePassword(String pass) {
+ delegate.requestObjectSigningKeyStorePass = pass;
+ }
+
+ @Override
+ public String getRequestObjectSigningKeyPassword() {
+ return delegate.requestObjectSigningKeyPass;
+ }
+
+ @Override
+ public void setRequestObjectSigningKeyPassword(String pass) {
+ delegate.requestObjectSigningKeyPass = pass;
+ }
+
+ @Override
+ public String getRequestObjectSigningKeystoreType() {
+ return delegate.requestObjectSigningKeystoreType;
+ }
+
+ @Override
+ public void setRequestObjectSigningKeystoreType(String type) {
+ delegate.requestObjectSigningKeystoreType = type;
+ }
+
+ @Override
+ public String getRequestObjectSigningKeyAlias() {
+ return delegate.requestObjectSigningKeyAlias;
+ }
+
+ @Override
+ public void setRequestObjectSigningKeyAlias(String alias) {
+ delegate.requestObjectSigningKeyAlias = alias;
+ }
+
+ @Override
+ public boolean getRequestParameterSupported() {
+ return delegate.requestParameterSupported;
+ }
+
+ @Override
+ public boolean getRequestUriParameterSupported() {
+ return delegate.requestUriParameterSupported;
+ }
+
}
protected String getAuthServerBaseUrl(OidcHttpFacade facade, String base) {
diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcJsonConfiguration.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcJsonConfiguration.java
index 5e65d60fe06..5fd8ffb2be6 100644
--- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcJsonConfiguration.java
+++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcJsonConfiguration.java
@@ -38,15 +38,16 @@
"resource", "public-client", "credentials",
"use-resource-role-mappings", "use-realm-role-mappings",
"enable-cors", "cors-max-age", "cors-allowed-methods", "cors-exposed-headers",
- "expose-token", "bearer-only", "autodetect-bearer-only",
- "connection-pool-size",
+ "expose-token", "bearer-only", "autodetect-bearer-only", "connection-pool-size",
"allow-any-hostname", "disable-trust-manager", "truststore", "truststore-password",
- "client-keystore", "client-keystore-password", "client-key-password",
- "always-refresh-token",
- "register-node-at-startup", "register-node-period", "token-store", "adapter-state-cookie-path", "principal-attribute",
- "proxy-url", "turn-off-change-session-id-on-login", "token-minimum-time-to-live",
- "min-time-between-jwks-requests", "public-key-cache-ttl",
- "ignore-oauth-query-parameter", "verify-token-audience", "token-signature-algorithm"
+ "client-keystore", "client-keystore-password", "request-object-signing-keystore-file",
+ "request-object-signing-keystore-password","request-object-signing-key-password", "request-object-signing-key-alias", "request-object-signing-keystore-type",
+ "always-refresh-token", "register-node-at-startup", "register-node-period", "token-store",
+ "adapter-state-cookie-path", "principal-attribute", "proxy-url", "turn-off-change-session-id-on-login",
+ "token-minimum-time-to-live", "min-time-between-jwks-requests", "public-key-cache-ttl",
+ "ignore-oauth-query-parameter", "verify-token-audience", "token-signature-algorithm",
+ "authentication-request-format", "request-object-signing-algorithm", "request-object-encryption-algorithm",
+ "request-object-content-encryption-algorithm"
})
public class OidcJsonConfiguration {
@@ -62,8 +63,16 @@ public class OidcJsonConfiguration {
protected String clientKeystore;
@JsonProperty("client-keystore-password")
protected String clientKeystorePassword;
- @JsonProperty("client-key-password")
- protected String clientKeyPassword;
+ @JsonProperty("request-object-signing-keystore-file")
+ protected String requestObjectSigningKeyStoreFile;
+ @JsonProperty("request-object-signing-keystore-password")
+ protected String requestObjectSigningKeystorePassword;
+ @JsonProperty("request-object-signing-key-password")
+ protected String requestObjectSigningKeyPassword;
+ @JsonProperty("request-object-signing-key-alias")
+ protected String requestObjectSigningKeyAlias;
+ @JsonProperty("request-object-signing-keystore-type")
+ protected String requestObjectSigningKeystoreType;
@JsonProperty("connection-pool-size")
protected int connectionPoolSize = 20;
@JsonProperty("always-refresh-token")
@@ -140,6 +149,18 @@ public class OidcJsonConfiguration {
@JsonProperty("token-signature-algorithm")
protected String tokenSignatureAlgorithm = DEFAULT_TOKEN_SIGNATURE_ALGORITHM;
+ @JsonProperty("authentication-request-format")
+ protected String authenticationRequestFormat;
+
+ @JsonProperty("request-object-signing-algorithm")
+ protected String requestSignatureAlgorithm;
+
+ @JsonProperty("request-object-encryption-algorithm")
+ protected String requestEncryptAlgorithm;
+
+ @JsonProperty("request-object-content-encryption-algorithm")
+ protected String requestContentEncryptionMethod;
+
/**
* The Proxy url to use for requests to the auth-server, configurable via the adapter config property {@code proxy-url}.
*/
@@ -178,6 +199,13 @@ public void setTruststorePassword(String truststorePassword) {
this.truststorePassword = truststorePassword;
}
+ public String getRequestObjectSigningKeyStoreFile() {
+ return requestObjectSigningKeyStoreFile;
+ }
+
+ public void setRequestObjectSigningKeyStoreFile(String requestObjectSigningKeyStoreFile) {
+ this.requestObjectSigningKeyStoreFile = requestObjectSigningKeyStoreFile;
+ }
public String getClientKeystore() {
return clientKeystore;
}
@@ -186,6 +214,22 @@ public void setClientKeystore(String clientKeystore) {
this.clientKeystore = clientKeystore;
}
+ public String getRequestObjectSigningKeystoreType() {
+ return requestObjectSigningKeystoreType;
+ }
+
+ public void setRequestObjectSigningKeystoreType(String type) {
+ this.requestObjectSigningKeystoreType = type;
+ }
+
+ public String getRequestObjectSigningKeyAlias() {
+ return requestObjectSigningKeyAlias;
+ }
+
+ public void setRequestObjectSigningKeyAlias(String alias) {
+ this.requestObjectSigningKeyAlias = alias;
+ }
+
public String getClientKeystorePassword() {
return clientKeystorePassword;
}
@@ -194,12 +238,20 @@ public void setClientKeystorePassword(String clientKeystorePassword) {
this.clientKeystorePassword = clientKeystorePassword;
}
- public String getClientKeyPassword() {
- return clientKeyPassword;
+ public String getRequestObjectSigningKeyPassword() {
+ return requestObjectSigningKeyPassword;
+ }
+
+ public String getRequestObjectSigningKeystorePassword() {
+ return requestObjectSigningKeystorePassword;
}
- public void setClientKeyPassword(String clientKeyPassword) {
- this.clientKeyPassword = clientKeyPassword;
+ public void setRequestObjectSigningKeystorePassword(String requestObjectSigningKeystorePassword) {
+ this.requestObjectSigningKeystorePassword = requestObjectSigningKeystorePassword;
+ }
+
+ public void setRequestObjectSigningKeyPassword(String requestObjectSigningKeyPassword) {
+ this.requestObjectSigningKeyPassword = requestObjectSigningKeyPassword;
}
public int getConnectionPoolSize() {
@@ -511,5 +563,36 @@ public void setTokenSignatureAlgorithm(String tokenSignatureAlgorithm) {
this.tokenSignatureAlgorithm = tokenSignatureAlgorithm;
}
+ public String getAuthenticationRequestFormat() {
+ return authenticationRequestFormat;
+ }
+
+ public void setAuthenticationRequestFormat(String authenticationRequestFormat) {
+ this.authenticationRequestFormat = authenticationRequestFormat;
+ }
+
+ public String getRequestSignatureAlgorithm() {
+ return requestSignatureAlgorithm;
+ }
+
+ public void setRequestSignatureAlgorithm(String requestSignatureAlgorithm) {
+ this.requestSignatureAlgorithm = requestSignatureAlgorithm;
+ }
+
+ public String getRequestEncryptAlgorithm() {
+ return requestEncryptAlgorithm;
+ }
+
+ public void setRequestEncryptAlgorithm(String requestSignatureAlgorithm) {
+ this.requestEncryptAlgorithm = requestSignatureAlgorithm;
+ }
+
+ public String getRequestContentEncryptionMethod() {
+ return requestContentEncryptionMethod;
+ }
+
+ public void setRequestContentEncryptionMethod (String requestContentEncryptionMethod) {
+ this.requestContentEncryptionMethod = requestContentEncryptionMethod;
+ }
}
diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcProviderMetadata.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcProviderMetadata.java
index 9984de7c023..6da737f5b14 100644
--- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcProviderMetadata.java
+++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcProviderMetadata.java
@@ -114,6 +114,9 @@ public class OidcProviderMetadata {
@JsonProperty("request_uri_parameter_supported")
private Boolean requestUriParameterSupported;
+ @JsonProperty("pushed_authorization_request_endpoint")
+ private String pushedAuthorizationRequestEndpoint;
+
@JsonProperty("revocation_endpoint")
private String revocationEndpoint;
@@ -142,6 +145,12 @@ public class OidcProviderMetadata {
@JsonProperty("tls_client_certificate_bound_access_tokens")
private Boolean tlsClientCertificateBoundAccessTokens;
+ @JsonProperty("request_object_encryption_enc_values_supported")
+ private List requestObjectContentEncryptionMethodsSupported;
+
+ @JsonProperty("request_object_encryption_alg_values_supported")
+ private List requestObjectEncryptionAlgValuesSupported;
+
protected Map otherClaims = new HashMap();
public String getIssuer() {
@@ -411,6 +420,30 @@ public Boolean getTlsClientCertificateBoundAccessTokens() {
return tlsClientCertificateBoundAccessTokens;
}
+ public List getRequestObjectEncryptionAlgValuesSupported() {
+ return requestObjectEncryptionAlgValuesSupported;
+ }
+
+ public void setRequestObjectEncryptionAlgValuesSupported(List requestObjectEncryptionAlgValuesSupported) {
+ this.requestObjectEncryptionAlgValuesSupported = requestObjectEncryptionAlgValuesSupported;
+ }
+
+ public List getRequestObjectContentEncryptionMethodsSupported() {
+ return requestObjectContentEncryptionMethodsSupported;
+ }
+
+ public void setRequestObjectContentEncryptionMethodsSupported(List requestObjectContentEncryptionMethodsSupported) {
+ this.requestObjectContentEncryptionMethodsSupported = requestObjectContentEncryptionMethodsSupported;
+ }
+
+ public String getPushedAuthorizationRequestEndpoint() {
+ return pushedAuthorizationRequestEndpoint;
+ }
+
+ public void setPushedAuthorizationRequestEndpoint (String url) {
+ this.pushedAuthorizationRequestEndpoint = url;
+ }
+
@JsonAnyGetter
public Map getOtherClaims() {
return otherClaims;
diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcPublicKeyExtractor.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcPublicKeyExtractor.java
new file mode 100644
index 00000000000..75b5294341d
--- /dev/null
+++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcPublicKeyExtractor.java
@@ -0,0 +1,36 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2023 Red Hat, Inc., and individual 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.wildfly.security.http.oidc;
+
+import org.wildfly.security.jose.jwk.JsonWebKeySet;
+
+/**
+ * An interface to send a GET request to the JWKSUrl of the OpenID
+ * provider and obtain the public key.
+ *
+ * @author Prarthona Paul
+ */
+public interface OidcPublicKeyExtractor {
+ /**
+ * @param config the OpenID Connect client configuration
+ * @return a Json Web Key Set with the public keys for the OpenID provider
+ */
+ JsonWebKeySet extractPublicKeySet(OidcClientConfiguration config) throws Exception;
+
+}
diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcRequestAuthenticator.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcRequestAuthenticator.java
index 6b51d980d97..716d610253c 100644
--- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcRequestAuthenticator.java
+++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcRequestAuthenticator.java
@@ -18,7 +18,11 @@
package org.wildfly.security.http.oidc;
+import static org.jose4j.jws.AlgorithmIdentifiers.NONE;
import static org.wildfly.security.http.oidc.ElytronMessages.log;
+import static org.jose4j.jws.AlgorithmIdentifiers.HMAC_SHA256;
+import static org.jose4j.jws.AlgorithmIdentifiers.HMAC_SHA384;
+import static org.jose4j.jws.AlgorithmIdentifiers.HMAC_SHA512;
import static org.wildfly.security.http.oidc.Oidc.CLIENT_ID;
import static org.wildfly.security.http.oidc.Oidc.CODE;
import static org.wildfly.security.http.oidc.Oidc.DOMAIN_HINT;
@@ -28,31 +32,57 @@
import static org.wildfly.security.http.oidc.Oidc.MAX_AGE;
import static org.wildfly.security.http.oidc.Oidc.OIDC_SCOPE;
import static org.wildfly.security.http.oidc.Oidc.PROMPT;
+import static org.wildfly.security.http.oidc.Oidc.PROTOCOL_CLASSPATH;
import static org.wildfly.security.http.oidc.Oidc.REDIRECT_URI;
import static org.wildfly.security.http.oidc.Oidc.RESPONSE_TYPE;
+import static org.wildfly.security.http.oidc.Oidc.REQUEST;
+import static org.wildfly.security.http.oidc.Oidc.REQUEST_URI;
import static org.wildfly.security.http.oidc.Oidc.SCOPE;
import static org.wildfly.security.http.oidc.Oidc.SESSION_STATE;
import static org.wildfly.security.http.oidc.Oidc.STATE;
import static org.wildfly.security.http.oidc.Oidc.UI_LOCALES;
+
+import static org.wildfly.security.http.oidc.Oidc.logToken;
import static org.wildfly.security.http.oidc.Oidc.generateId;
import static org.wildfly.security.http.oidc.Oidc.getQueryParamValue;
-import static org.wildfly.security.http.oidc.Oidc.logToken;
import static org.wildfly.security.http.oidc.Oidc.stripQueryParam;
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
import java.io.IOException;
+import java.io.Reader;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.security.Key;
+import java.security.KeyPair;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
-import org.apache.http.HttpStatus;
+import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
+import org.jose4j.jwa.AlgorithmConstraints;
+import org.jose4j.jwe.JsonWebEncryption;
+import org.jose4j.jws.JsonWebSignature;
+import org.jose4j.jwt.JwtClaims;
+import org.jose4j.jwt.consumer.InvalidJwtException;
+import org.jose4j.keys.HmacKey;
+import org.jose4j.lang.JoseException;
import org.wildfly.security.http.HttpConstants;
+import org.wildfly.security.jose.jwk.JWK;
+import org.wildfly.security.jose.jwk.JWKParser;
+
/**
* @author Bill Burke
@@ -180,18 +210,68 @@ protected String getRedirectUri(String state) {
if (deployment.getAuthUrl() == null) {
return null;
}
- URIBuilder redirectUriBuilder = new URIBuilder(deployment.getAuthUrl())
- .addParameter(RESPONSE_TYPE, CODE)
- .addParameter(CLIENT_ID, deployment.getResourceName())
- .addParameter(REDIRECT_URI, rewrittenRedirectUri(url))
- .addParameter(STATE, state);
- redirectUriBuilder.addParameters(forwardedQueryParams);
+
+ String redirectUri = rewrittenRedirectUri(url);
+ URIBuilder redirectUriBuilder = new URIBuilder(deployment.getAuthUrl());
+ redirectUriBuilder.addParameter(RESPONSE_TYPE, CODE)
+ .addParameter(CLIENT_ID, deployment.getResourceName());
+
+ switch (deployment.getAuthenticationRequestFormat()) {
+ case REQUEST:
+ if (deployment.getRequestParameterSupported()) {
+ // add request objects into request parameter
+ createRequestWithRequestParameter(REQUEST, redirectUriBuilder, redirectUri, state, forwardedQueryParams);
+ } else {
+ // send request as usual
+ createOAuthRequest(redirectUriBuilder, redirectUri, state, forwardedQueryParams);
+ log.requestParameterNotSupported();
+ }
+ break;
+ case REQUEST_URI:
+ if (deployment.getRequestUriParameterSupported()) {
+ createRequestWithRequestParameter(REQUEST_URI, redirectUriBuilder, redirectUri, state, forwardedQueryParams);
+ } else {
+ // send request as usual
+ createOAuthRequest(redirectUriBuilder, redirectUri, state, forwardedQueryParams);
+ log.requestParameterNotSupported();
+ }
+ break;
+ default:
+ createOAuthRequest(redirectUriBuilder, redirectUri, state, forwardedQueryParams);
+ break;
+ }
return redirectUriBuilder.build().toString();
} catch (URISyntaxException e) {
throw log.unableToCreateRedirectResponse(e);
+ } catch (IOException | JoseException | InvalidJwtException e) {
+ throw new RuntimeException(e);
}
}
+ protected URIBuilder createOAuthRequest(URIBuilder redirectUriBuilder, String redirectUri, String state, List forwardedQueryParams) {
+ redirectUriBuilder.addParameter(REDIRECT_URI, redirectUri)
+ .addParameter(STATE, state)
+ .addParameters(forwardedQueryParams);
+ return redirectUriBuilder;
+ }
+
+ protected URIBuilder createRequestWithRequestParameter(String requestFormat, URIBuilder redirectUriBuilder, String redirectUri, String state, List forwardedQueryParams) throws JoseException, IOException, InvalidJwtException {
+ String request = convertToRequestParameter(redirectUriBuilder, redirectUri, state, forwardedQueryParams);
+
+ switch (requestFormat) {
+ case REQUEST:
+ redirectUriBuilder.addParameter(REDIRECT_URI, redirectUri)
+ .addParameter(REQUEST, request);
+ break;
+ case REQUEST_URI:
+ String request_uri = getRequestUri(request);
+ redirectUriBuilder.addParameter(REQUEST_URI, request_uri)
+ .addParameter(REDIRECT_URI, redirectUri);
+ break;
+ }
+ return redirectUriBuilder;
+ }
+
protected int getSSLRedirectPort() {
return sslRedirectPort;
}
@@ -416,4 +496,119 @@ private static boolean hasScope(String scopeParam, String targetScope) {
}
return false;
}
+ private String convertToRequestParameter(URIBuilder redirectUriBuilder, String redirectUri, String state, List forwardedQueryParams) throws IOException, JoseException {
+ redirectUriBuilder.addParameter(SCOPE, OIDC_SCOPE);
+
+ JwtClaims jwtClaims = new JwtClaims();
+ jwtClaims.setIssuer(deployment.getIssuerUrl());
+ jwtClaims.setAudience(deployment.getProviderUrl());
+
+ for ( NameValuePair parameter: forwardedQueryParams) {
+ jwtClaims.setClaim(parameter.getName(), parameter.getValue());
+ }
+ jwtClaims.setClaim(STATE, state);
+ jwtClaims.setClaim(REDIRECT_URI, redirectUri);
+ jwtClaims.setClaim(RESPONSE_TYPE, CODE);
+ jwtClaims.setClaim(CLIENT_ID, deployment.getResourceName());
+
+ // sign JWT first before encrypting
+ JsonWebSignature signedRequest = signRequest(jwtClaims);
+
+ // Encrypting optional
+ if (deployment.getRequestEncryptAlgorithm() != null && !deployment.getRequestEncryptAlgorithm().isEmpty() &&
+ deployment.getRequestContentEncryptionMethod() != null && !deployment.getRequestContentEncryptionMethod().isEmpty()) {
+ return encryptRequest(signedRequest).getCompactSerialization();
+ } else {
+ return signedRequest.getCompactSerialization();
+ }
+ }
+
+ public KeyPair getkeyPair() throws IOException {
+ if (deployment.getRequestObjectSigningKeystoreFile().contains(PROTOCOL_CLASSPATH)) {
+ deployment.setRequestObjectSigningKeystoreFile(deployment.getRequestObjectSigningKeystoreFile().replace(PROTOCOL_CLASSPATH, Objects.requireNonNull(Thread.currentThread().getContextClassLoader().getResource("")).getPath()));
+ }
+ if (!deployment.getRequestSignatureAlgorithm().equals(NONE) && deployment.getRequestObjectSigningKeystoreFile() == null){
+ throw log.invalidKeyStoreConfiguration();
+ } else {
+ return new JWTSigningUtils().loadKeyPairFromKeyStore(deployment.getRequestObjectSigningKeystoreFile(),
+ deployment.getRequestObjectSigningKeyStorePassword(), deployment.getRequestObjectSigningKeyPassword(),
+ deployment.getRequestObjectSigningKeyAlias(), deployment.getRequestObjectSigningKeystoreType());
+ }
+ }
+
+ public JsonWebSignature signRequest(JwtClaims jwtClaims) throws IOException, JoseException {
+ JsonWebSignature jsonWebSignature = new JsonWebSignature();
+ jsonWebSignature.setPayload(jwtClaims.toJson());
+
+ if (!deployment.getRequestObjectSigningAlgValuesSupported().contains(deployment.getRequestSignatureAlgorithm())) {
+ throw log.invalidRequestObjectSignatureAlgorithm();
+ } else {
+ if (deployment.getRequestSignatureAlgorithm().contains(NONE)) { //unsigned
+ jsonWebSignature.setAlgorithmConstraints(AlgorithmConstraints.NO_CONSTRAINTS);
+ jsonWebSignature.setAlgorithmHeaderValue(NONE);
+ } else if (deployment.getRequestSignatureAlgorithm().contains(HMAC_SHA256)
+ || deployment.getRequestSignatureAlgorithm().contains(HMAC_SHA384)
+ || deployment.getRequestSignatureAlgorithm().contains(HMAC_SHA512)) { //signed with symmetric key
+ jsonWebSignature.setAlgorithmHeaderValue(deployment.getRequestSignatureAlgorithm());
+ Key key = new HmacKey(deployment.getResourceCredentials().get("secret").toString().getBytes(StandardCharsets.UTF_8)); //the client secret is a shared secret between the server and the client
+ jsonWebSignature.setDoKeyValidation(false); //skips validation so that size of the secret does not matter
+ jsonWebSignature.setKey(key);
+ } else { //signed with asymmetric key
+ KeyPair keyPair = getkeyPair();
+ jsonWebSignature.setKey(keyPair.getPrivate());
+ jsonWebSignature.setAlgorithmHeaderValue(deployment.getRequestSignatureAlgorithm());
+ }
+ jsonWebSignature.sign();
+ return jsonWebSignature;
+ }
+ }
+
+ private JsonWebEncryption encryptRequest(JsonWebSignature signedRequest) throws IOException, JoseException {
+ if (!deployment.getRequestObjectEncryptionAlgValuesSupported().contains(deployment.getRequestEncryptAlgorithm())) {
+ throw log.invalidRequestObjectEncryptionAlgorithm();
+ } else if (!deployment.getRequestObjectContentEncryptionMethodsSupported().contains(deployment.getRequestContentEncryptionMethod())) {
+ throw log.invalidRequestObjectContentEncryptionAlgorithm();
+ } else {
+ JsonWebEncryption jsonEncryption = new JsonWebEncryption();
+ jsonEncryption.setPayload(signedRequest.getCompactSerialization());
+ jsonEncryption.setAlgorithmConstraints(new AlgorithmConstraints(AlgorithmConstraints.ConstraintType.PERMIT, deployment.getRequestEncryptAlgorithm(), deployment.getRequestContentEncryptionMethod()));
+ jsonEncryption.setAlgorithmHeaderValue(deployment.getRequestEncryptAlgorithm());
+ jsonEncryption.setEncryptionMethodHeaderParameter(deployment.getRequestContentEncryptionMethod());
+ JWK[] jwkList = new JWKPublicKeySetExtractor().extractPublicKeySet(deployment).getKeys();
+ for (JWK jwk : jwkList) {
+ if (jwk.getPublicKeyUse().equals("enc")) { //JWTs are to be encrypted with public keys shared by the OpenID provider and decrypted by the private key they hold
+ jsonEncryption.setKey(new JWKParser(jwk).toPublicKey());
+ break;
+ }
+ }
+ return jsonEncryption;
+ }
+ }
+
+ private String getRequestUri(String request) throws IOException, InvalidJwtException {
+ HttpPost parRequest = new HttpPost(deployment.getPushedAuthorizationRequestEndpoint());
+ List formParams = new ArrayList();
+ formParams.add(new BasicNameValuePair(REQUEST, request));
+ ClientCredentialsProviderUtils.setClientCredentials(deployment, parRequest, formParams);
+ parRequest.addHeader("Content-type", "application/x-www-form-urlencoded");
+
+ UrlEncodedFormEntity form = new UrlEncodedFormEntity(formParams, StandardCharsets.UTF_8);
+ parRequest.setEntity(form);
+ HttpResponse response = deployment.getClient().execute(parRequest);
+ if (response.getStatusLine().getStatusCode() != HttpStatus.SC_CREATED) {
+ EntityUtils.consumeQuietly(response.getEntity());
+ throw log.unexpectedResponseCodeFromOidcProvider(response.getStatusLine().getStatusCode());
+ }
+ InputStream inputStream = response.getEntity().getContent();
+ StringBuilder textBuilder = new StringBuilder();
+ try (Reader reader = new BufferedReader(new InputStreamReader
+ (inputStream, StandardCharsets.UTF_8))) {
+ int c = 0;
+ while ((c = reader.read()) != -1) {
+ textBuilder.append((char) c);
+ }
+ }
+ JwtClaims jwt = JwtClaims.parse(textBuilder.toString());
+ return jwt.getClaimValueAsString(REQUEST_URI);
+ }
}
diff --git a/http/oidc/src/test/java/org/wildfly/security/http/oidc/KeycloakConfiguration.java b/http/oidc/src/test/java/org/wildfly/security/http/oidc/KeycloakConfiguration.java
index 5dfa052ed28..7c8c246f280 100644
--- a/http/oidc/src/test/java/org/wildfly/security/http/oidc/KeycloakConfiguration.java
+++ b/http/oidc/src/test/java/org/wildfly/security/http/oidc/KeycloakConfiguration.java
@@ -18,11 +18,15 @@
package org.wildfly.security.http.oidc;
+import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Base64;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
+import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
@@ -30,9 +34,11 @@
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.RolesRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
-
+import org.wildfly.security.ssl.test.util.CAGenerationTool;
import io.restassured.RestAssured;
+import javax.security.auth.x500.X500Principal;
+
/**
* Keycloak configuration for testing.
*
@@ -47,6 +53,24 @@ public class KeycloakConfiguration {
private static final String BOB = "bob";
private static final String BOB_PASSWORD = "bob123+";
public static final String ALLOWED_ORIGIN = "http://somehost";
+ public static final boolean EMAIL_VERIFIED = false;
+ public static final String RSA_KEYSTORE_FILE_NAME = "jwt.keystore";
+ public static final String EC_KEYSTORE_FILE_NAME = "jwtEC.keystore";
+ public static final String KEYSTORE_ALIAS = "jwtKeystore";
+ public static final String KEYSTORE_PASS = "Elytron";
+ public static final String PKCS12_KEYSTORE_TYPE = "PKCS12";
+ public static String KEYSTORE_CLASSPATH;
+
+ /* Accepted Request Object Encrypting Algorithms for KeyCloak*/
+ public static final String RSA_OAEP = "RSA-OAEP";
+ public static final String RSA_OAEP_256 = "RSA-OAEP-256";
+ public static final String RSA1_5 = "RSA1_5";
+
+ /* Accepted Request Object Encryption Methods for KeyCloak*/
+ public static final String A128CBC_HS256 = "A128CBC-HS256";
+ public static final String A192CBC_HS384 = "A192CBC-HS384";
+ public static final String A256CBC_HS512 = "A256CBC-HS512";
+ public static CAGenerationTool caGenerationTool = null;
/**
* Configure RealmRepresentation as follows:
@@ -60,14 +84,14 @@ public class KeycloakConfiguration {
*
*/
public static RealmRepresentation getRealmRepresentation(final String realmName, String clientId, String clientSecret,
- String clientHostName, int clientPort, String clientApp) {
+ String clientHostName, int clientPort, String clientApp) throws Exception {
return createRealm(realmName, clientId, clientSecret, clientHostName, clientPort, clientApp);
}
public static RealmRepresentation getRealmRepresentation(final String realmName, String clientId, String clientSecret,
String clientHostName, int clientPort, String clientApp,
boolean directAccessGrantEnabled, String bearerOnlyClientId,
- String corsClientId) {
+ String corsClientId) throws Exception {
return createRealm(realmName, clientId, clientSecret, clientHostName, clientPort, clientApp, directAccessGrantEnabled, bearerOnlyClientId, corsClientId);
}
@@ -102,14 +126,14 @@ public static String getAccessToken(String authServerUrl, String realmName, Stri
}
private static RealmRepresentation createRealm(String name, String clientId, String clientSecret,
- String clientHostName, int clientPort, String clientApp) {
+ String clientHostName, int clientPort, String clientApp) throws Exception {
return createRealm(name, clientId, clientSecret, clientHostName, clientPort, clientApp, false, null, null);
}
private static RealmRepresentation createRealm(String name, String clientId, String clientSecret,
String clientHostName, int clientPort, String clientApp,
boolean directAccessGrantEnabled, String bearerOnlyClientId,
- String corsClientId) {
+ String corsClientId) throws Exception {
RealmRepresentation realm = new RealmRepresentation();
realm.setRealm(name);
@@ -140,15 +164,16 @@ private static RealmRepresentation createRealm(String name, String clientId, Str
realm.getUsers().add(createUser(ALICE, ALICE_PASSWORD, Arrays.asList(USER_ROLE, ADMIN_ROLE)));
realm.getUsers().add(createUser(BOB, BOB_PASSWORD, Arrays.asList(USER_ROLE)));
+
return realm;
}
- private static ClientRepresentation createWebAppClient(String clientId, String clientSecret, String clientHostName, int clientPort, String clientApp, boolean directAccessGrantEnabled) {
+ private static ClientRepresentation createWebAppClient(String clientId, String clientSecret, String clientHostName, int clientPort, String clientApp, boolean directAccessGrantEnabled) throws Exception {
return createWebAppClient(clientId, clientSecret, clientHostName, clientPort, clientApp, directAccessGrantEnabled, null);
}
private static ClientRepresentation createWebAppClient(String clientId, String clientSecret, String clientHostName, int clientPort,
- String clientApp, boolean directAccessGrantEnabled, String allowedOrigin) {
+ String clientApp, boolean directAccessGrantEnabled, String allowedOrigin) throws Exception {
ClientRepresentation client = new ClientRepresentation();
client.setClientId(clientId);
client.setPublicClient(false);
@@ -157,9 +182,20 @@ private static ClientRepresentation createWebAppClient(String clientId, String c
client.setRedirectUris(Arrays.asList("http://" + clientHostName + ":" + clientPort + "/" + clientApp));
client.setEnabled(true);
client.setDirectAccessGrantsEnabled(directAccessGrantEnabled);
+
if (allowedOrigin != null) {
client.setWebOrigins(Collections.singletonList(allowedOrigin));
}
+ OIDCAdvancedConfigWrapper oidcAdvancedConfigWrapper = OIDCAdvancedConfigWrapper.fromClientRepresentation(client);
+ oidcAdvancedConfigWrapper.setUseJwksUrl(false);
+ KEYSTORE_CLASSPATH = Objects.requireNonNull(KeycloakConfiguration.class.getClassLoader().getResource("")).getPath();
+ caGenerationTool = CAGenerationTool.builder()
+ .setBaseDir(KEYSTORE_CLASSPATH)
+ .setRequestIdentities(CAGenerationTool.Identity.values()) // Create all identities.
+ .build();
+ X500Principal principal = new X500Principal("OU=Elytron, O=Elytron, C=UK, ST=Elytron, CN=OcspResponder");
+ X509Certificate rsaCert = caGenerationTool.createIdentity(KEYSTORE_ALIAS, principal, RSA_KEYSTORE_FILE_NAME, CAGenerationTool.Identity.CA);
+ client.getAttributes().put("jwt.credential.certificate", Base64.getEncoder().encodeToString(rsaCert.getEncoded()));
return client;
}
diff --git a/http/oidc/src/test/java/org/wildfly/security/http/oidc/OidcTest.java b/http/oidc/src/test/java/org/wildfly/security/http/oidc/OidcTest.java
index 9ce5a55c934..6ef4ccf450d 100644
--- a/http/oidc/src/test/java/org/wildfly/security/http/oidc/OidcTest.java
+++ b/http/oidc/src/test/java/org/wildfly/security/http/oidc/OidcTest.java
@@ -18,10 +18,29 @@
package org.wildfly.security.http.oidc;
+import static org.jose4j.jws.AlgorithmIdentifiers.NONE;
+import static org.jose4j.jws.AlgorithmIdentifiers.RSA_USING_SHA256;
+import static org.jose4j.jws.AlgorithmIdentifiers.RSA_USING_SHA512;
+import static org.jose4j.jws.AlgorithmIdentifiers.HMAC_SHA256;
+import static org.jose4j.jws.AlgorithmIdentifiers.HMAC_SHA512;
+import static org.jose4j.jws.AlgorithmIdentifiers.RSA_PSS_USING_SHA256;
+import static org.wildfly.security.http.oidc.KeycloakConfiguration.KEYSTORE_CLASSPATH;
+import static org.wildfly.security.http.oidc.KeycloakConfiguration.KEYSTORE_PASS;
+import static org.wildfly.security.http.oidc.KeycloakConfiguration.PKCS12_KEYSTORE_TYPE;
+import static org.wildfly.security.http.oidc.KeycloakConfiguration.RSA1_5;
+import static org.wildfly.security.http.oidc.KeycloakConfiguration.RSA_OAEP;
+import static org.wildfly.security.http.oidc.KeycloakConfiguration.RSA_OAEP_256;
+import static org.wildfly.security.http.oidc.KeycloakConfiguration.A128CBC_HS256;
+import static org.wildfly.security.http.oidc.KeycloakConfiguration.A192CBC_HS384;
+import static org.wildfly.security.http.oidc.KeycloakConfiguration.A256CBC_HS512;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import static org.wildfly.security.http.oidc.Oidc.OIDC_NAME;
+import static org.wildfly.security.http.oidc.Oidc.OIDC_SCOPE;
+import static org.wildfly.security.http.oidc.Oidc.AuthenticationFormat.REQUEST_TYPE_OAUTH2;
+import static org.wildfly.security.http.oidc.Oidc.AuthenticationFormat.REQUEST_TYPE_REQUEST;
+import static org.wildfly.security.http.oidc.Oidc.AuthenticationFormat.REQUEST_TYPE_REQUEST_URI;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
@@ -30,6 +49,8 @@
import java.util.HashMap;
import java.util.Map;
+import okhttp3.mockwebserver.MockWebServer;
+import okhttp3.mockwebserver.QueueDispatcher;
import org.apache.http.HttpStatus;
import org.junit.AfterClass;
import org.junit.BeforeClass;
@@ -40,8 +61,6 @@
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import io.restassured.RestAssured;
-import okhttp3.mockwebserver.MockWebServer;
-import okhttp3.mockwebserver.QueueDispatcher;
/**
* Tests for the OpenID Connect authentication mechanism.
@@ -163,8 +182,102 @@ public void testTokenSignatureAlgorithm() throws Exception {
}
// Note: The tests will fail if `localhost` is not listed first in `/etc/hosts` file for the loopback addresses (IPv4 and IPv6).
+ @Test
+ public void testOpenIDWithOauth2Request() throws Exception {
+ performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(REQUEST_TYPE_OAUTH2.getValue(), "", "", ""), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT);
+ }
+
+ @Test
+ public void testOpenIDWithPlaintextRequest() throws Exception {
+ performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(REQUEST_TYPE_REQUEST.getValue(), NONE, "", ""), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT);
+ }
+
+ @Test
+ public void testOpenIDWithPlaintextEncryptedRequest() throws Exception {
+ performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(REQUEST_TYPE_REQUEST.getValue(), NONE, RSA_OAEP, A128CBC_HS256), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT);
+ }
+
+ @Test
+ public void testOpenIDWithRsaSignedAndEncryptedRequest() throws Exception {
+ performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(REQUEST_TYPE_REQUEST.getValue(), RSA_USING_SHA512, RSA_OAEP, A192CBC_HS384, KEYSTORE_CLASSPATH + KeycloakConfiguration.RSA_KEYSTORE_FILE_NAME, KeycloakConfiguration.KEYSTORE_ALIAS, PKCS12_KEYSTORE_TYPE), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT);
+ }
+
+ @Test
+ public void testOpenIDWithPsSignedAndRsaEncryptedRequest() throws Exception {
+ performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(REQUEST_TYPE_REQUEST.getValue(), RSA_PSS_USING_SHA256, RSA_OAEP_256, A256CBC_HS512, KEYSTORE_CLASSPATH + KeycloakConfiguration.RSA_KEYSTORE_FILE_NAME, KeycloakConfiguration.KEYSTORE_ALIAS, PKCS12_KEYSTORE_TYPE), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT);
+ }
+
+ @Test
+ public void testOpenIDWithInvalidSignAlgorithm() throws Exception {
+ //RSNULL is a valid signature algorithm, but not one of the ones supported by keycloak
+ performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(REQUEST_TYPE_REQUEST.getValue(), "RSNULL", RSA_OAEP_256, A256CBC_HS512, KEYSTORE_CLASSPATH + KeycloakConfiguration.RSA_KEYSTORE_FILE_NAME, KeycloakConfiguration.KEYSTORE_ALIAS, PKCS12_KEYSTORE_TYPE), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT, true);
+ }
+
+ @Test
+ public void testOpenIDWithRsaSignedRequest() throws Exception {
+ performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(REQUEST_TYPE_REQUEST.getValue(), RSA_USING_SHA256, "", "", KEYSTORE_CLASSPATH + KeycloakConfiguration.RSA_KEYSTORE_FILE_NAME, KeycloakConfiguration.KEYSTORE_ALIAS, PKCS12_KEYSTORE_TYPE), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT);
+ }
+
+ @Test
+ public void testOpenIDWithPsSignedRequest() throws Exception {
+ performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(REQUEST_TYPE_REQUEST.getValue(), RSA_PSS_USING_SHA256, "", "", KEYSTORE_CLASSPATH + KeycloakConfiguration.RSA_KEYSTORE_FILE_NAME, KeycloakConfiguration.KEYSTORE_ALIAS, PKCS12_KEYSTORE_TYPE), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT);
+ }
+ @Test
+ public void testOpenIDWithInvalidRequestEncryptionAlgorithm() throws Exception {
+ // None is not a valid algorithm for encrypting jwt's and RSA-OAEP is not a valid algorithm for signing
+ performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(REQUEST_TYPE_REQUEST.getValue(), RSA1_5, NONE, NONE, KEYSTORE_CLASSPATH + KeycloakConfiguration.RSA_KEYSTORE_FILE_NAME, KeycloakConfiguration.KEYSTORE_ALIAS, PKCS12_KEYSTORE_TYPE), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT, true);
+ }
+
+ @Test
+ public void testOpenIDWithPlaintextRequestUri() throws Exception {
+ performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(REQUEST_TYPE_REQUEST_URI.getValue(), NONE, "", ""), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT);
+ }
+
+ @Test
+ public void testOpenIDWithHmacRequestUri() throws Exception {
+ performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(REQUEST_TYPE_REQUEST.getValue(), HMAC_SHA256, "", ""), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT);
+ }
+
+ @Test
+ public void testOpenIDWithHmacEncryptedRequestUri() throws Exception {
+ performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(REQUEST_TYPE_REQUEST.getValue(), HMAC_SHA512, RSA_OAEP, A128CBC_HS256), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT);
+ }
+
+ @Test
+ public void testOpenIDWithSignedAndEncryptedRequestUri() throws Exception {
+ performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(REQUEST_TYPE_REQUEST_URI.getValue(), RSA_USING_SHA256, RSA_OAEP_256, A256CBC_HS512, KEYSTORE_CLASSPATH + KeycloakConfiguration.RSA_KEYSTORE_FILE_NAME, KeycloakConfiguration.KEYSTORE_ALIAS, PKCS12_KEYSTORE_TYPE), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT);
+ }
+
private void performAuthentication(InputStream oidcConfig, String username, String password, boolean loginToKeycloak,
int expectedDispatcherStatusCode, String expectedLocation, String clientPageText) throws Exception {
+ performAuthentication(oidcConfig, username, password, loginToKeycloak, expectedDispatcherStatusCode, expectedLocation, clientPageText, null, false);
+ }
+
+ private void performAuthentication(InputStream oidcConfig, String username, String password, boolean loginToKeycloak,
+ int expectedDispatcherStatusCode, String expectedLocation, String clientPageText, String expectedScope, boolean checkInvalidScopeError) throws Exception {
+ performAuthentication(oidcConfig, username, password, loginToKeycloak, expectedDispatcherStatusCode, expectedLocation, clientPageText, expectedScope, checkInvalidScopeError, false);
+ }
+
+ private void performAuthentication(InputStream oidcConfig, String username, String password, boolean loginToKeycloak,
+ int expectedDispatcherStatusCode, String expectedLocation, String clientPageText, boolean checkInvalidRequestAlgorithm) throws Exception {
+ performAuthentication(oidcConfig, username, password, loginToKeycloak, expectedDispatcherStatusCode, expectedLocation, clientPageText, null, false, checkInvalidRequestAlgorithm);
+ }
+
+ private void performAuthentication(InputStream oidcConfig, String username, String password, boolean loginToKeycloak,
+ int expectedDispatcherStatusCode, String expectedLocation, String clientPageText, String expectedScope, boolean checkInvalidScopeError, boolean checkInvalidRequestAlgorithm) throws Exception {
try {
Map props = new HashMap<>();
OidcClientConfiguration oidcClientConfiguration = OidcClientConfigurationBuilder.build(oidcConfig);
@@ -176,10 +289,29 @@ private void performAuthentication(InputStream oidcConfig, String username, Stri
URI requestUri = new URI(getClientUrl());
TestingHttpServerRequest request = new TestingHttpServerRequest(null, requestUri);
- mechanism.evaluateRequest(request);
+ try {
+ mechanism.evaluateRequest(request);
+ } catch (Exception e) {
+ if (checkInvalidRequestAlgorithm) {
+ assertTrue(e.getMessage().contains("java.io.IOException: ELY23058"));
+ return; //Expected to get an exception and ignore the rest
+ } else {
+ throw e;
+ }
+ }
TestingHttpServerResponse response = request.getResponse();
assertEquals(loginToKeycloak ? HttpStatus.SC_MOVED_TEMPORARILY : HttpStatus.SC_FORBIDDEN, response.getStatusCode());
assertEquals(Status.NO_AUTH, request.getResult());
+ if (expectedScope != null) {
+ assertTrue(response.getFirstResponseHeaderValue("Location").contains("scope=" + expectedScope));
+ }
+ if (oidcClientConfiguration.getAuthenticationRequestFormat().contains(REQUEST_TYPE_REQUEST_URI.getValue())) {
+ assertTrue(response.getFirstResponseHeaderValue("Location").contains("scope=" + OIDC_SCOPE));
+ assertTrue(response.getFirstResponseHeaderValue("Location").contains("request_uri="));
+ } else if (oidcClientConfiguration.getAuthenticationRequestFormat().contains(REQUEST_TYPE_REQUEST.getValue())) {
+ assertTrue(response.getFirstResponseHeaderValue("Location").contains("scope=" + OIDC_SCOPE));
+ assertTrue(response.getFirstResponseHeaderValue("Location").contains("request="));
+ }
if (loginToKeycloak) {
client.setDispatcher(createAppResponse(mechanism, expectedDispatcherStatusCode, expectedLocation, clientPageText));
@@ -291,4 +423,44 @@ private InputStream getOidcConfigurationInputStreamWithTokenSignatureAlgorithm()
"}";
return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8));
}
+
+ private InputStream getOidcConfigurationInputStreamWithRequestParameter(String requestParameter, String signAlgorithm, String encryptAlgorithm, String encMethod){
+ String oidcConfig = "{\n" +
+ " \"client-id\" : \"" + CLIENT_ID + "\",\n" +
+ " \"provider-url\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM + "/" + "\",\n" +
+ " \"public-client\" : \"false\",\n" +
+ " \"ssl-required\" : \"EXTERNAL\",\n" +
+ " \"authentication-request-format\" : \"" + requestParameter + "\",\n" +
+ " \"request-object-signing-algorithm\" : \"" + signAlgorithm + "\",\n" +
+ " \"request-object-encryption-algorithm\" : \"" + encryptAlgorithm + "\",\n" +
+ " \"request-object-content-encryption-algorithm\" : \"" + encMethod + "\",\n" +
+ " \"credentials\" : {\n" +
+ " \"secret\" : \"" + CLIENT_SECRET + "\"\n" +
+ " }\n" +
+ "}";
+ return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8));
+ }
+
+ private InputStream getOidcConfigurationInputStreamWithRequestParameter(String requestParameter, String signAlgorithm, String encryptAlgorithm, String encMethod, String keyStorePath, String alias, String keyStoreType){
+ String oidcConfig = "{\n" +
+ " \"client-id\" : \"" + CLIENT_ID + "\",\n" +
+ " \"provider-url\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM + "/" + "\",\n" +
+ " \"public-client\" : \"false\",\n" +
+ " \"ssl-required\" : \"EXTERNAL\",\n" +
+ " \"authentication-request-format\" : \"" + requestParameter + "\",\n" +
+ " \"request-object-signing-algorithm\" : \"" + signAlgorithm + "\",\n" +
+ " \"request-object-encryption-algorithm\" : \"" + encryptAlgorithm + "\",\n" +
+ " \"request-object-content-encryption-algorithm\" : \"" + encMethod + "\",\n" +
+ " \"request-object-signing-keystore-file\" : \"" + keyStorePath + "\",\n" +
+ " \"request-object-signing-keystore-type\" : \"" + keyStoreType + "\",\n" +
+ " \"request-object-signing-keystore-password\" : \"" + KEYSTORE_PASS + "\",\n" +
+ " \"request-object-signing-key-password\" : \"" + KEYSTORE_PASS + "\",\n" +
+ " \"request-object-signing-key-alias\" : \"" + alias + "\",\n" +
+ " \"credentials\" : {\n" +
+ " \"secret\" : \"" + CLIENT_SECRET + "\"\n" +
+ " }\n" +
+ "}";
+ return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8));
+ }
}
+
diff --git a/pom.xml b/pom.xml
index dd4eebaa9d2..feda2be1d08 100644
--- a/pom.xml
+++ b/pom.xml
@@ -98,6 +98,7 @@
4.3.3
2.40.0
2.3.0
+ 3.1.0.Final
INFO
@@ -1146,6 +1147,12 @@
${version.org.bouncycastle}
test
+
+ org.keycloak
+ keycloak-services
+ ${version.org.keycloak.keycloak-services}
+ test
+