diff --git a/http/oidc/pom.xml b/http/oidc/pom.xml
index 64a7f7285d..5f3a6504fd 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 ac5e2861fc..e836cc3b46 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
@@ -18,10 +18,10 @@
package org.wildfly.security.http.oidc;
+import static org.jboss.logging.annotations.Message.NONE;
import static org.jboss.logging.Logger.Level.DEBUG;
import static org.jboss.logging.Logger.Level.ERROR;
import static org.jboss.logging.Logger.Level.WARN;
-import static org.jboss.logging.annotations.Message.NONE;
import java.io.IOException;
@@ -238,5 +238,45 @@ interface ElytronMessages extends BasicLogger {
@Message(id = 23057, value = "principal-attribute '%s' claim does not exist, falling back to 'sub'")
void principalAttributeClaimDoesNotExist(String principalAttributeClaim);
+ @Message(id = 23058, value = "Invalid keystore configuration for signing Request Objects.")
+ IOException invalidKeyStoreConfiguration();
+
+ @Message(id = 23059, value = "The signature algorithm specified is not supported by the OpenID Provider.")
+ IOException invalidRequestObjectSignatureAlgorithm();
+
+ @Message(id = 23060, value = "The encryption algorithm specified is not supported by the OpenID Provider.")
+ IOException invalidRequestObjectEncryptionAlgorithm();
+
+ @Message(id = 23061, value = "The content encryption algorithm (enc value) specified is not supported by the OpenID Provider.")
+ IOException invalidRequestObjectEncryptionEncValue();
+
+ @LogMessage(level = WARN)
+ @Message(id = 23062, value = "The OpenID provider does not support request parameters. Sending the request using OAuth2 format.")
+ void requestParameterNotSupported();
+
+ @Message(id = 23063, value = "Both request object encryption algorithm and request object content encryption algorithm must be configured to encrypt the request object.")
+ IllegalArgumentException invalidRequestObjectEncryptionAlgorithmConfiguration();
+
+ @Message(id = 23064, value = "Failed to create the authentication request using the request parameter.")
+ RuntimeException unableToCreateRequestWithRequestParameter(@Cause Exception cause);
+
+ @Message(id = 23065, value = "Failed to create the authentication request using the request_uri parameter.")
+ RuntimeException unableToCreateRequestUriWithRequestParameter(@Cause Exception cause);
+
+ @Message (id = 23066, value = "Failed to send a request to the OpenID provider's Pushed Authorization Request endpoint.")
+ RuntimeException failedToSendPushedAuthorizationRequest(@Cause Exception cause);
+
+ @Message(id = 23067, value = "Cannot retrieve the request_uri as the pushed authorization request endpoint is not available for the OpenID provider.")
+ RuntimeException pushedAuthorizationRequestEndpointNotAvailable();
+
+ @LogMessage(level = WARN)
+ @Message(id = 23068, value = "The request object will be unsigned. This should not be used in a production environment. To sign the request object, for use in a production environment, please specify the request object signing algorithm.")
+ void unsignedRequestObjectIsUsed();
+
+ @Message(id = 23069, value = "The client secret has not been configured. Unable to sign the request object using the client secret.")
+ RuntimeException clientSecretNotConfigured();
+
+ @Message(id = 23070, value = "Authentication request format must be one of the following: oauth2, request, request_uri.")
+ RuntimeException invalidAuthenticationRequestFormat();
}
diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/JWKEncPublicKeyLocator.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/JWKEncPublicKeyLocator.java
new file mode 100644
index 0000000000..819e595067
--- /dev/null
+++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/JWKEncPublicKeyLocator.java
@@ -0,0 +1,113 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2024 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.ElytronMessages.log;
+import static org.wildfly.security.http.oidc.Oidc.JSON_CONTENT_TYPE;
+
+import java.security.PublicKey;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.List;
+
+import org.apache.http.client.methods.HttpGet;
+import org.wildfly.security.jose.jwk.JWK;
+import org.wildfly.security.jose.jwk.JsonWebKeySet;
+import org.wildfly.security.jose.jwk.JsonWebKeySetUtil;
+
+/**
+ * A public key locator that dynamically obtains the public key used for encryption
+ * from an OpenID provider by sending a request to the provider's {@code jwks_uri}
+ * when needed.
+ *
+ * @author Prarthona Paul
+ * */
+class JWKEncPublicKeyLocator implements PublicKeyLocator {
+ private List currentKeys = new ArrayList<>();
+
+ private volatile int lastRequestTime = 0;
+
+ @Override
+ public PublicKey getPublicKey(String kid, OidcClientConfiguration config) {
+ int minTimeBetweenRequests = config.getMinTimeBetweenJwksRequests();
+ int publicKeyCacheTtl = config.getPublicKeyCacheTtl();
+ int currentTime = getCurrentTime();
+
+ PublicKey publicKey = lookupCachedKey(publicKeyCacheTtl, currentTime);
+ if (publicKey != null) {
+ return publicKey;
+ }
+
+ synchronized (this) {
+ currentTime = getCurrentTime();
+ if (currentTime > lastRequestTime + minTimeBetweenRequests) {
+ sendRequest(config);
+ lastRequestTime = currentTime;
+ } else {
+ log.debug("Won't send request to jwks url. Last request time was " + lastRequestTime);
+ }
+ return lookupCachedKey(publicKeyCacheTtl, currentTime);
+ }
+
+ }
+
+ @Override
+ public void reset(OidcClientConfiguration config) {
+ synchronized (this) {
+ sendRequest(config);
+ lastRequestTime = getCurrentTime();
+ }
+ }
+
+ private PublicKey lookupCachedKey(int publicKeyCacheTtl, int currentTime) {
+ if (lastRequestTime + publicKeyCacheTtl > currentTime) {
+ return currentKeys.get(0); // returns the first cached public key
+ } else {
+ return null;
+ }
+ }
+
+ private static int getCurrentTime() {
+ return (int) (System.currentTimeMillis() / 1000);
+ }
+
+ private void sendRequest(OidcClientConfiguration config) {
+ if (log.isTraceEnabled()) {
+ log.trace("Going to send request to retrieve new set of public keys to encrypt a JWT request for client " + config.getResourceName());
+ }
+
+ HttpGet request = new HttpGet(config.getJwksUrl());
+ request.addHeader(ACCEPT, JSON_CONTENT_TYPE);
+ try {
+ JsonWebKeySet jwks = Oidc.sendJsonHttpRequest(config, request, JsonWebKeySet.class);
+ Map publicKeys = JsonWebKeySetUtil.getKeysForUse(jwks, JWK.Use.ENC);
+
+ if (log.isDebugEnabled()) {
+ log.debug("Public keys successfully retrieved for client " + config.getResourceName() + ". New kids: " + publicKeys.keySet());
+ }
+
+ // update current keys
+ currentKeys.clear();
+ currentKeys.addAll(publicKeys.values());
+ } catch (OidcException e) {
+ log.error("Error when sending request to retrieve public keys", e);
+ }
+ }
+}
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 4da8d3a538..13df213373 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
@@ -19,18 +19,13 @@
package org.wildfly.security.http.oidc;
import static org.wildfly.security.http.oidc.ElytronMessages.log;
+import static org.wildfly.security.http.oidc.JWTSigningUtils.loadKeyPairFromKeyStore;
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;
@@ -155,43 +150,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/JWTSigningUtils.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/JWTSigningUtils.java
new file mode 100644
index 0000000000..03546d8a23
--- /dev/null
+++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/JWTSigningUtils.java
@@ -0,0 +1,78 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2024 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;
+
+/**
+ * A utility class to obtain the KeyPair from a keystore file.
+ *
+ * @author Prarthona Paul
+ */
+
+class JWTSigningUtils {
+
+ public 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) {
+ throw log.unableToLoadKeyWithAlias(keyAlias);
+ }
+ PublicKey publicKey = keyStore.getCertificate(keyAlias).getPublicKey();
+ return new KeyPair(publicKey, privateKey);
+ } catch (Exception e) {
+ throw log.unableToLoadPrivateKey(e);
+ }
+ }
+
+ public static InputStream findFile(String keystoreFile) {
+ if (keystoreFile.startsWith(PROTOCOL_CLASSPATH)) {
+ String classPathLocation = keystoreFile.replace(PROTOCOL_CLASSPATH, "");
+ // try current class classloader first
+ InputStream is = JWTSigningUtils.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 f42313b7f5..575809f2f4 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
@@ -45,6 +45,7 @@
public class Oidc {
public static final String ACCEPT = "Accept";
+ public static final String AUTHENTICATION_REQUEST_FORMAT = "authentication-request-format";
public static final String OIDC_NAME = "OIDC";
public static final String JSON_CONTENT_TYPE = "application/json";
public static final String HTML_CONTENT_TYPE = "text/html";
@@ -74,6 +75,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";
@@ -201,6 +204,27 @@ public enum TokenStore {
COOKIE
}
+ public enum AuthenticationRequestFormat {
+ OAUTH2("oauth2"),
+ REQUEST("request"),
+ REQUEST_URI("request_uri");
+
+ private final String value;
+
+ AuthenticationRequestFormat(String value) {
+ this.value = value;
+ }
+
+ /**
+ * Get the string value for this authentication format.
+ *
+ * @return the string value for this authentication format
+ */
+ 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 3e18fb4eb6..ca56da2863 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 requestObjectEncryptionEncValuesSupported;
+ protected List requestObjectEncryptionAlgValuesSupported;
+ protected boolean requestParameterSupported;
+ protected boolean requestUriParameterSupported;
protected String resource;
protected String clientId;
@@ -126,6 +132,17 @@ public enum RelativeUrlsUsed {
protected boolean verifyTokenAudience = false;
protected String tokenSignatureAlgorithm = DEFAULT_TOKEN_SIGNATURE_ALGORITHM;
+ protected String authenticationRequestFormat;
+ protected String requestObjectSigningAlgorithm;
+ protected String requestObjectEncryptionAlgValue;
+ protected String requestObjectEncryptionEncValue;
+ protected String pushedAuthorizationRequestEndpoint;
+ protected String requestObjectSigningKeyStoreFile;
+ protected String requestObjectSigningKeyStorePassword;
+ protected String requestObjectSigningKeyPassword;
+ protected String requestObjectSigningKeyAlias;
+ protected String requestObjectSigningKeyStoreType;
+ protected JWKEncPublicKeyLocator encryptionPublicKeyLocator;
public OidcClientConfiguration() {
}
@@ -223,6 +240,13 @@ protected void resolveUrls() {
tokenUrl = config.getTokenEndpoint();
logoutUrl = config.getLogoutEndpoint();
jwksUrl = config.getJwksUri();
+ requestParameterSupported = config.getRequestParameterSupported();
+ requestObjectSigningAlgValuesSupported = config.getRequestObjectSigningAlgValuesSupported();
+ requestObjectEncryptionEncValuesSupported = config.getRequestObjectEncryptionEncValuesSupported();
+ requestObjectEncryptionAlgValuesSupported = config.getRequestObjectEncryptionAlgValuesSupported();
+ requestUriParameterSupported = config.getRequestUriParameterSupported();
+ pushedAuthorizationRequestEndpoint = config.getPushedAuthorizationRequestEndpoint();
+
if (authServerBaseUrl != null) {
// keycloak-specific properties
accountUrl = getUrl(issuerUrl, ACCOUNT_PATH);
@@ -246,7 +270,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 +353,26 @@ public String getIssuerUrl() {
return issuerUrl;
}
+ public List getRequestObjectSigningAlgValuesSupported() {
+ return requestObjectSigningAlgValuesSupported;
+ }
+
+ public List getRequestObjectEncryptionAlgValuesSupported() {
+ return requestObjectEncryptionAlgValuesSupported;
+ }
+
+ public List getRequestObjectEncryptionEncValuesSupported() {
+ return requestObjectEncryptionEncValuesSupported;
+ }
+
+ public boolean getRequestParameterSupported() {
+ return requestParameterSupported;
+ }
+
+ public boolean getRequestUriParameterSupported() {
+ return requestUriParameterSupported;
+ }
+
public void setResource(String resource) {
this.resource = resource;
}
@@ -648,4 +692,91 @@ public String getTokenSignatureAlgorithm() {
return tokenSignatureAlgorithm;
}
+ public String getAuthenticationRequestFormat() {
+ return authenticationRequestFormat;
+ }
+
+ public void setAuthenticationRequestFormat(String authenticationRequestFormat) {
+ this.authenticationRequestFormat = authenticationRequestFormat;
+ }
+
+ public String getRequestObjectSigningAlgorithm() {
+ return requestObjectSigningAlgorithm;
+ }
+
+ public void setRequestObjectSigningAlgorithm(String requestObjectSigningAlgorithm) {
+ this.requestObjectSigningAlgorithm = requestObjectSigningAlgorithm;
+ }
+
+ public String getRequestObjectEncryptionAlgValue() {
+ return requestObjectEncryptionAlgValue;
+ }
+
+ public void setRequestObjectEncryptionAlgValue(String requestObjectEncryptionAlgValue) {
+ this.requestObjectEncryptionAlgValue = requestObjectEncryptionAlgValue;
+ }
+
+ public String getRequestObjectEncryptionEncValue() {
+ return requestObjectEncryptionEncValue;
+ }
+
+ public void setRequestObjectEncryptionEncValue(String requestObjectEncryptionEncValue) {
+ this.requestObjectEncryptionEncValue = requestObjectEncryptionEncValue;
+ }
+
+ public String getRequestObjectSigningKeyStoreFile() {
+ return requestObjectSigningKeyStoreFile;
+ }
+
+ public void setRequestObjectSigningKeyStoreFile(String keyStoreFile) {
+ this.requestObjectSigningKeyStoreFile = keyStoreFile;
+ }
+
+ public String getRequestObjectSigningKeyStorePassword() {
+ return requestObjectSigningKeyStorePassword;
+ }
+
+ public void setRequestObjectSigningKeyStorePassword(String requestObjectSigningKeyStorePassword) {
+ this.requestObjectSigningKeyStorePassword = requestObjectSigningKeyStorePassword;
+ }
+
+ public String getRequestObjectSigningKeyPassword() {
+ return requestObjectSigningKeyPassword;
+ }
+
+ public void setRequestObjectSigningKeyPassword(String requestObjectSigningKeyPassword) {
+ this.requestObjectSigningKeyPassword = requestObjectSigningKeyPassword;
+ }
+
+ public String getRequestObjectSigningKeyStoreType() {
+ return requestObjectSigningKeyStoreType;
+ }
+
+ public void setRequestObjectSigningKeyStoreType(String requestObjectSigningKeyStoreType) {
+ this.requestObjectSigningKeyStoreType = requestObjectSigningKeyStoreType;
+ }
+
+ public String getRequestObjectSigningKeyAlias() {
+ return requestObjectSigningKeyAlias;
+ }
+
+ public void setRequestObjectSigningKeyAlias(String requestObjectSigningKeyAlias) {
+ this.requestObjectSigningKeyAlias = requestObjectSigningKeyAlias;
+ }
+
+ public String getPushedAuthorizationRequestEndpoint() {
+ return pushedAuthorizationRequestEndpoint;
+ }
+
+ public void setPushedAuthorizationRequestEndpoint(String pushedAuthorizationRequestEndpoint) {
+ this.pushedAuthorizationRequestEndpoint = pushedAuthorizationRequestEndpoint;
+ }
+
+ public void setEncryptionPublicKeyLocator(JWKEncPublicKeyLocator publicKeySetExtractor) {
+ this.encryptionPublicKeyLocator = publicKeySetExtractor;
+ }
+
+ public JWKEncPublicKeyLocator getEncryptionPublicKeyLocator() {
+ return this.encryptionPublicKeyLocator;
+ }
}
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 f2d757e493..43bebace9f 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
@@ -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.wildfly.security.http.oidc.Oidc.AuthenticationRequestFormat.OAUTH2;
+import static org.wildfly.security.http.oidc.Oidc.AuthenticationRequestFormat.REQUEST;
+import static org.wildfly.security.http.oidc.Oidc.AuthenticationRequestFormat.REQUEST_URI;
import static org.wildfly.security.http.oidc.Oidc.SSLRequired;
import static org.wildfly.security.http.oidc.Oidc.TokenStore;
@@ -103,6 +107,41 @@ protected OidcClientConfiguration internalBuild(final OidcJsonConfiguration oidc
if (oidcJsonConfiguration.getScope() != null) {
oidcClientConfiguration.setScope(oidcJsonConfiguration.getScope());
}
+ if (oidcJsonConfiguration.getAuthenticationRequestFormat() != null) {
+ if (!(oidcJsonConfiguration.getAuthenticationRequestFormat().equals(OAUTH2.getValue()) ||
+ oidcJsonConfiguration.getAuthenticationRequestFormat().equals(REQUEST.getValue()) ||
+ oidcJsonConfiguration.getAuthenticationRequestFormat().equals(REQUEST_URI.getValue()))) {
+ throw log.invalidAuthenticationRequestFormat();
+ }
+ oidcClientConfiguration.setAuthenticationRequestFormat(oidcJsonConfiguration.getAuthenticationRequestFormat());
+ } else {
+ oidcClientConfiguration.setAuthenticationRequestFormat(OAUTH2.getValue());
+ }
+ if (oidcJsonConfiguration.getRequestObjectSigningAlgorithm() != null) {
+ oidcClientConfiguration.setRequestObjectSigningAlgorithm(oidcJsonConfiguration.getRequestObjectSigningAlgorithm());
+ } else {
+ oidcClientConfiguration.setRequestObjectSigningAlgorithm(NONE);
+ }
+ if (oidcJsonConfiguration.getRequestObjectEncryptionAlgValue() != null && oidcJsonConfiguration.getRequestObjectEncryptionEncValue() != null) { //both are required to encrypt the request object
+ oidcClientConfiguration.setRequestObjectEncryptionAlgValue(oidcJsonConfiguration.getRequestObjectEncryptionAlgValue());
+ oidcClientConfiguration.setRequestObjectEncryptionEncValue(oidcJsonConfiguration.getRequestObjectEncryptionEncValue());
+ JWKEncPublicKeyLocator encryptionPublicKeyLocator = new JWKEncPublicKeyLocator();
+ oidcClientConfiguration.setEncryptionPublicKeyLocator(encryptionPublicKeyLocator);
+ } else if (oidcJsonConfiguration.getRequestObjectEncryptionAlgValue() != null || oidcJsonConfiguration.getRequestObjectEncryptionEncValue() != null) { //if only one is specified, that is not correct
+ throw log.invalidRequestObjectEncryptionAlgorithmConfiguration();
+ }
+ 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());
@@ -193,8 +232,8 @@ 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 3c249bb846..f5d930bd52 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 getRequestObjectSigningAlgorithm() {
+ return delegate.getRequestObjectSigningAlgorithm();
+ }
+
+ @Override
+ public void setRequestObjectSigningAlgorithm(String requestSignature) {
+ delegate.setRequestObjectSigningAlgorithm(requestSignature);
+ }
+
+ @Override
+ public String getRequestObjectEncryptionAlgValue() {
+ return delegate.getRequestObjectEncryptionAlgValue();
+ }
+
+ @Override
+ public void setRequestObjectEncryptionAlgValue(String requestObjectEncryptionAlgValue) {
+ delegate.setRequestObjectEncryptionAlgValue(requestObjectEncryptionAlgValue);
+ }
+
+ @Override
+ public String getRequestObjectEncryptionEncValue() {
+ return delegate.requestObjectEncryptionEncValue;
+ }
+
+ @Override
+ public void setRequestObjectEncryptionEncValue (String requestObjectEncryptionEncValue) {
+ delegate.requestObjectEncryptionEncValue = requestObjectEncryptionEncValue;
+ }
+
+ @Override
+ public String getRequestObjectSigningKeyStoreFile() {
+ return delegate.requestObjectSigningKeyStoreFile;
+ }
+
+ @Override
+ public void setRequestObjectSigningKeyStoreFile(String keyStoreFile) {
+ delegate.requestObjectSigningKeyStoreFile = keyStoreFile;
+ }
+
+ @Override
+ public String getRequestObjectSigningKeyStorePassword() {
+ return delegate.requestObjectSigningKeyStorePassword;
+ }
+
+ @Override
+ public void setRequestObjectSigningKeyStorePassword(String requestObjectSigningKeyStorePassword) {
+ delegate.requestObjectSigningKeyStorePassword = requestObjectSigningKeyStorePassword;
+ }
+
+ @Override
+ public String getRequestObjectSigningKeyPassword() {
+ return delegate.requestObjectSigningKeyPassword;
+ }
+
+ @Override
+ public void setRequestObjectSigningKeyPassword(String requestObjectSigningKeyPassword) {
+ delegate.requestObjectSigningKeyPassword = requestObjectSigningKeyPassword;
+ }
+
+ @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 f835cc4fbc..29d2d785e3 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,18 @@
"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", "scope"
+ "ignore-oauth-query-parameter", "verify-token-audience", "token-signature-algorithm", "scope",
+ "authentication-request-format", "request-object-signing-algorithm", "request-object-encryption-alg-value",
+ "request-object-encryption-enc-value", "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"
})
public class OidcJsonConfiguration {
@@ -64,6 +67,16 @@ public class OidcJsonConfiguration {
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")
@@ -142,6 +155,17 @@ public class OidcJsonConfiguration {
@JsonProperty("scope")
protected String scope;
+ @JsonProperty("authentication-request-format")
+ protected String authenticationRequestFormat;
+
+ @JsonProperty("request-object-signing-algorithm")
+ protected String requestObjectSigningAlgorithm;
+
+ @JsonProperty("request-object-encryption-alg-value")
+ protected String requestObjectEncryptionAlgValue;
+
+ @JsonProperty("request-object-encryption-enc-value")
+ protected String requestObjectEncryptionEncValue;
/**
* The Proxy url to use for requests to the auth-server, configurable via the adapter config property {@code proxy-url}.
@@ -181,6 +205,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;
}
@@ -189,6 +220,22 @@ public void setClientKeystore(String clientKeystore) {
this.clientKeystore = clientKeystore;
}
+ public String getRequestObjectSigningKeyStoreType() {
+ return requestObjectSigningKeyStoreType;
+ }
+
+ public void setRequestObjectSigningKeyStoreType(String requestObjectSigningKeyStoreType) {
+ this.requestObjectSigningKeyStoreType = requestObjectSigningKeyStoreType;
+ }
+
+ public String getRequestObjectSigningKeyAlias() {
+ return requestObjectSigningKeyAlias;
+ }
+
+ public void setRequestObjectSigningKeyAlias(String requestObjectSigningKeyAlias) {
+ this.requestObjectSigningKeyAlias = requestObjectSigningKeyAlias;
+ }
+
public String getClientKeystorePassword() {
return clientKeystorePassword;
}
@@ -201,10 +248,26 @@ 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() {
return connectionPoolSize;
}
@@ -521,5 +584,36 @@ public String getScope() {
public void setScope(String scope) {
this.scope = scope;
}
+ public String getAuthenticationRequestFormat() {
+ return authenticationRequestFormat;
+ }
+
+ public void setAuthenticationRequestFormat(String authenticationRequestFormat) {
+ this.authenticationRequestFormat = authenticationRequestFormat;
+ }
+
+ public String getRequestObjectSigningAlgorithm() {
+ return requestObjectSigningAlgorithm;
+ }
+
+ public void setRequestObjectSigningAlgorithm(String requestObjectSigningAlgorithm) {
+ this.requestObjectSigningAlgorithm = requestObjectSigningAlgorithm;
+ }
+
+ public String getRequestObjectEncryptionAlgValue() {
+ return requestObjectEncryptionAlgValue;
+ }
+
+ public void setRequestObjectEncryptionAlgValue(String requestObjectEncryptionAlgValue) {
+ this.requestObjectEncryptionAlgValue = requestObjectEncryptionAlgValue;
+ }
+
+ public String getRequestObjectEncryptionEncValue() {
+ return requestObjectEncryptionEncValue;
+ }
+
+ public void setRequestObjectEncryptionEncValue (String requestObjectEncryptionEncValue) {
+ this.requestObjectEncryptionEncValue = requestObjectEncryptionEncValue;
+ }
}
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 9984de7c02..6c964dbfe1 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 requestObjectEncryptionEncValuesSupported;
+
+ @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 getRequestObjectEncryptionEncValuesSupported() {
+ return requestObjectEncryptionEncValuesSupported;
+ }
+
+ public void setRequestObjectEncryptionEncValuesSupported(List requestObjectEncryptionEncValuesSupported) {
+ this.requestObjectEncryptionEncValuesSupported = requestObjectEncryptionEncValuesSupported;
+ }
+
+ 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/OidcRequestAuthenticator.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcRequestAuthenticator.java
index bf67e93859..5ef5c26122 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,6 +18,10 @@
package org.wildfly.security.http.oidc;
+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.jose4j.jws.AlgorithmIdentifiers.NONE;
import static org.wildfly.security.http.oidc.ElytronMessages.log;
import static org.wildfly.security.http.oidc.Oidc.ALLOW_QUERY_PARAMS_PROPERTY_NAME;
import static org.wildfly.security.http.oidc.Oidc.CLIENT_ID;
@@ -32,13 +36,17 @@
import static org.wildfly.security.http.oidc.Oidc.PROMPT;
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.ClientCredentialsProviderType.SECRET;
+
+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.IOException;
@@ -47,6 +55,10 @@
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
+import java.nio.charset.StandardCharsets;
+import java.security.Key;
+import java.security.KeyPair;
+import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
@@ -54,10 +66,16 @@
import java.util.Map;
import java.util.Set;
-import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
+import org.apache.http.HttpStatus;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.message.BasicNameValuePair;
+import org.jose4j.jwa.AlgorithmConstraints;
+import org.jose4j.jwe.JsonWebEncryption;
+import org.jose4j.jws.JsonWebSignature;
+import org.jose4j.jwt.JwtClaims;
+import org.jose4j.keys.HmacKey;
+import org.jose4j.lang.JoseException;
import org.wildfly.security.http.HttpConstants;
/**
@@ -201,18 +219,73 @@ protected String getRedirectUri(String state) {
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
+ try {
+ createRequestWithRequestParameter(REQUEST, redirectUriBuilder, redirectUri, state, forwardedQueryParams);
+ } catch (IOException | JoseException e) {
+ throw log.unableToCreateRequestWithRequestParameter(e);
+ }
+ } else {
+ // send request as usual
+ createOAuthRequest(redirectUriBuilder, redirectUri, state, forwardedQueryParams);
+ log.requestParameterNotSupported();
+ }
+ break;
+ case REQUEST_URI:
+ if (deployment.getRequestUriParameterSupported()) {
+ try {
+ createRequestWithRequestParameter(REQUEST_URI, redirectUriBuilder, redirectUri, state, forwardedQueryParams);
+ } catch (IOException | JoseException e) {
+ throw log.unableToCreateRequestUriWithRequestParameter(e);
+ }
+ } 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);
}
}
+ 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 {
+ 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 = ServerRequest.getRequestUri(request, deployment);
+ redirectUriBuilder.addParameter("request_uri", request_uri)
+ .addParameter(REDIRECT_URI, redirectUri);
+ break;
+ }
+ return redirectUriBuilder;
+ }
+
protected int getSSLRedirectPort() {
return sslRedirectPort;
}
@@ -461,4 +534,92 @@ private void addScopes(String scopes, Set allScopes) {
allScopes.addAll(Arrays.asList(scopes.split("\\s+")));
}
}
+
+ private String convertToRequestParameter(URIBuilder redirectUriBuilder, String redirectUri, String state, List forwardedQueryParams) throws JoseException, IOException {
+ redirectUriBuilder.addParameter(SCOPE, OIDC_SCOPE);
+
+ JwtClaims jwtClaims = new JwtClaims();
+ jwtClaims.setIssuer(deployment.getResourceName());
+ jwtClaims.setAudience(deployment.getIssuerUrl());
+
+ 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, deployment);
+
+ // Encrypting optional
+ if (deployment.getRequestObjectEncryptionAlgValue() != null && !deployment.getRequestObjectEncryptionAlgValue().isEmpty() &&
+ deployment.getRequestObjectEncryptionEncValue() != null && !deployment.getRequestObjectEncryptionEncValue().isEmpty()) {
+ return encryptRequest(signedRequest).getCompactSerialization();
+ } else {
+ return signedRequest.getCompactSerialization();
+ }
+ }
+
+ private static KeyPair getkeyPair(OidcClientConfiguration deployment) throws IOException {
+ if (!deployment.getRequestObjectSigningAlgorithm().equals(NONE) && deployment.getRequestObjectSigningKeyStoreFile() == null){
+ throw log.invalidKeyStoreConfiguration();
+ } else {
+ return JWTSigningUtils.loadKeyPairFromKeyStore(deployment.getRequestObjectSigningKeyStoreFile(),
+ deployment.getRequestObjectSigningKeyStorePassword(), deployment.getRequestObjectSigningKeyPassword(),
+ deployment.getRequestObjectSigningKeyAlias(), deployment.getRequestObjectSigningKeyStoreType());
+ }
+ }
+
+ private static JsonWebSignature signRequest(JwtClaims jwtClaims, OidcClientConfiguration deployment) throws IOException, JoseException {
+ JsonWebSignature jsonWebSignature = new JsonWebSignature();
+ jsonWebSignature.setPayload(jwtClaims.toJson());
+
+ if (!deployment.getRequestObjectSigningAlgValuesSupported().contains(deployment.getRequestObjectSigningAlgorithm())) {
+ throw log.invalidRequestObjectSignatureAlgorithm();
+ } else {
+ if (deployment.getRequestObjectSigningAlgorithm().equals(NONE)) { //unsigned
+ jsonWebSignature.setAlgorithmConstraints(AlgorithmConstraints.NO_CONSTRAINTS);
+ jsonWebSignature.setAlgorithmHeaderValue(NONE);
+ } else if (deployment.getRequestObjectSigningAlgorithm().equals(HMAC_SHA256)
+ || deployment.getRequestObjectSigningAlgorithm().equals(HMAC_SHA384)
+ || deployment.getRequestObjectSigningAlgorithm().equals(HMAC_SHA512)) { //signed with symmetric key
+ jsonWebSignature.setAlgorithmHeaderValue(deployment.getRequestObjectSigningAlgorithm());
+ String secretKey = (String) deployment.getResourceCredentials().get(SECRET.getValue());
+ if (secretKey == null) {
+ throw log.clientSecretNotConfigured();
+ } else {
+ Key key = new HmacKey(secretKey.getBytes(StandardCharsets.UTF_8)); //the client secret is a shared secret between the server and the client
+ jsonWebSignature.setKey(key);
+ }
+ } else { //signed with asymmetric key
+ KeyPair keyPair = getkeyPair(deployment);
+ jsonWebSignature.setKey(keyPair.getPrivate());
+ jsonWebSignature.setAlgorithmHeaderValue(deployment.getRequestObjectSigningAlgorithm());
+ }
+ if (!deployment.getRequestObjectSigningAlgorithm().equals(NONE))
+ jsonWebSignature.sign();
+ else
+ log.unsignedRequestObjectIsUsed();
+ return jsonWebSignature;
+ }
+ }
+
+ private JsonWebEncryption encryptRequest(JsonWebSignature signedRequest) throws JoseException, IOException {
+ if (!deployment.getRequestObjectEncryptionAlgValuesSupported().contains(deployment.getRequestObjectEncryptionAlgValue())) {
+ throw log.invalidRequestObjectEncryptionAlgorithm();
+ } else if (!deployment.getRequestObjectEncryptionEncValuesSupported().contains(deployment.getRequestObjectEncryptionEncValue())) {
+ throw log.invalidRequestObjectEncryptionEncValue();
+ } else {
+ JsonWebEncryption jsonEncryption = new JsonWebEncryption();
+ jsonEncryption.setPayload(signedRequest.getCompactSerialization());
+ jsonEncryption.setAlgorithmConstraints(new AlgorithmConstraints(AlgorithmConstraints.ConstraintType.PERMIT, deployment.getRequestObjectEncryptionAlgValue(), deployment.getRequestObjectEncryptionEncValue()));
+ jsonEncryption.setAlgorithmHeaderValue(deployment.getRequestObjectEncryptionAlgValue());
+ jsonEncryption.setEncryptionMethodHeaderParameter(deployment.getRequestObjectEncryptionEncValue());
+ PublicKey encPublicKey = deployment.getEncryptionPublicKeyLocator().getPublicKey(null, deployment);
+ jsonEncryption.setKey(encPublicKey);
+ return jsonEncryption;
+ }
+ }
}
diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/ServerRequest.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/ServerRequest.java
index ad50d715c5..3a203541ee 100644
--- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/ServerRequest.java
+++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/ServerRequest.java
@@ -25,13 +25,14 @@
import static org.wildfly.security.http.oidc.Oidc.KEYCLOAK_CLIENT_CLUSTER_HOST;
import static org.wildfly.security.http.oidc.Oidc.PASSWORD;
import static org.wildfly.security.http.oidc.Oidc.REDIRECT_URI;
+import static org.wildfly.security.http.oidc.Oidc.REQUEST;
import static org.wildfly.security.http.oidc.Oidc.USERNAME;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
-import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
@@ -46,6 +47,8 @@
import org.apache.http.client.methods.HttpPost;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
+import org.jose4j.jwt.JwtClaims;
+import org.jose4j.jwt.consumer.InvalidJwtException;
import org.wildfly.security.jose.util.JsonSerialization;
/**
@@ -274,4 +277,34 @@ public static AccessAndIDTokenResponse getBearerToken(OidcClientConfiguration oi
}
return tokenResponse;
}
+
+ public static String getRequestUri(String request, OidcClientConfiguration deployment) throws OidcException {
+ if (deployment.getPushedAuthorizationRequestEndpoint() == null) {
+ throw log.pushedAuthorizationRequestEndpointNotAvailable();
+ }
+ HttpPost parRequest = new HttpPost(deployment.getPushedAuthorizationRequestEndpoint());
+ List formParams = new ArrayList();
+ formParams.add(new BasicNameValuePair(REQUEST, request));
+ ClientCredentialsProviderUtils.setClientCredentials(deployment, parRequest, formParams);
+
+ UrlEncodedFormEntity form = new UrlEncodedFormEntity(formParams, StandardCharsets.UTF_8);
+ parRequest.setEntity(form);
+
+ HttpResponse response;
+ try {
+ response = deployment.getClient().execute(parRequest);
+ } catch (Exception e) {
+ throw log.failedToSendPushedAuthorizationRequest(e);
+ }
+ if (response.getStatusLine().getStatusCode() != HttpStatus.SC_CREATED) {
+ EntityUtils.consumeQuietly(response.getEntity());
+ throw log.unexpectedResponseCodeFromOidcProvider(response.getStatusLine().getStatusCode());
+ }
+ try (InputStream inputStream = response.getEntity().getContent()) {
+ JwtClaims jwt = JwtClaims.parse(readString(inputStream, StandardCharsets.UTF_8));
+ return jwt.getClaimValueAsString("request_uri");
+ } catch (IOException | InvalidJwtException e) {
+ throw log.failedToDecodeRequestUri(e);
+ }
+ }
}
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 4bb5e2b33b..8ebf4051bf 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
@@ -20,12 +20,23 @@
import static org.wildfly.security.http.oidc.OidcBaseTest.TENANT1_REALM;
import static org.wildfly.security.http.oidc.OidcBaseTest.TENANT2_REALM;
+import static org.wildfly.security.http.oidc.Oidc.OIDC_SCOPE;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.security.KeyStore;
+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 javax.security.auth.x500.X500Principal;
+import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
@@ -33,10 +44,9 @@
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 static org.wildfly.security.http.oidc.Oidc.OIDC_SCOPE;
/**
* Keycloak configuration for testing.
@@ -53,6 +63,24 @@ public class KeycloakConfiguration {
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;
+ public X509Certificate caCertificate = null;
// the users below are for multi-tenancy tests specifically
public static final String TENANT1_USER = "tenant1_user";
@@ -76,20 +104,20 @@ public class KeycloakConfiguration {
*
*/
public static RealmRepresentation getRealmRepresentation(final String realmName, String clientId, String clientSecret,
- String clientHostName, int clientPort, String clientApp, boolean configureClientScopes) {
+ String clientHostName, int clientPort, String clientApp, boolean configureClientScopes) throws Exception {
return createRealm(realmName, clientId, clientSecret, clientHostName, clientPort, clientApp, configureClientScopes);
}
public static RealmRepresentation getRealmRepresentation(final String realmName, String clientId, String clientSecret,
String clientHostName, int clientPort, String clientApp, int accessTokenLifespan,
- int ssoSessionMaxLifespan, boolean configureClientScopes, boolean multiTenancyApp) {
+ int ssoSessionMaxLifespan, boolean configureClientScopes, boolean multiTenancyApp) throws Exception {
return createRealm(realmName, clientId, clientSecret, clientHostName, clientPort, clientApp, accessTokenLifespan, ssoSessionMaxLifespan, configureClientScopes, multiTenancyApp);
}
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);
}
@@ -126,25 +154,25 @@ public static String getAccessToken(String authServerUrl, String realmName, Stri
private static RealmRepresentation createRealm(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, false);
}
private static RealmRepresentation createRealm(String name, String clientId, String clientSecret,
- String clientHostName, int clientPort, String clientApp, boolean configureClientScopes) {
+ String clientHostName, int clientPort, String clientApp, boolean configureClientScopes) throws Exception {
return createRealm(name, clientId, clientSecret, clientHostName, clientPort, clientApp, false, null, null, configureClientScopes);
}
private static RealmRepresentation createRealm(String name, String clientId, String clientSecret,
String clientHostName, int clientPort, String clientApp, int accessTokenLifeSpan, int ssoSessionMaxLifespan,
- boolean configureClientScopes, boolean multiTenancyApp) {
+ boolean configureClientScopes, boolean multiTenancyApp) throws Exception {
return createRealm(name, clientId, clientSecret, clientHostName, clientPort, clientApp, false, null, null, accessTokenLifeSpan, ssoSessionMaxLifespan, configureClientScopes, multiTenancyApp);
}
private static RealmRepresentation createRealm(String name, String clientId, String clientSecret,
String clientHostName, int clientPort, String clientApp,
boolean directAccessGrantEnabled, String bearerOnlyClientId,
- String corsClientId, boolean configureClientScopes) {
+ String corsClientId, boolean configureClientScopes) throws Exception {
return createRealm(name, clientId, clientSecret, clientHostName, clientPort, clientApp, directAccessGrantEnabled, bearerOnlyClientId, corsClientId, 3, 3, configureClientScopes, false);
}
@@ -152,7 +180,7 @@ private static RealmRepresentation createRealm(String name, String clientId, Str
String clientHostName, int clientPort, String clientApp,
boolean directAccessGrantEnabled, String bearerOnlyClientId,
String corsClientId, int accessTokenLifespan, int ssoSessionMaxLifespan,
- boolean configureClientScopes, boolean multiTenancyApp) {
+ boolean configureClientScopes, boolean multiTenancyApp) throws Exception {
RealmRepresentation realm = new RealmRepresentation();
realm.setRealm(name);
realm.setEnabled(true);
@@ -201,17 +229,12 @@ private static RealmRepresentation createRealm(String name, String clientId, Str
}
private static ClientRepresentation createWebAppClient(String clientId, String clientSecret, String clientHostName, int clientPort, String clientApp,
- boolean directAccessGrantEnabled, boolean multiTenancyApp) {
+ boolean directAccessGrantEnabled, boolean multiTenancyApp) throws Exception {
return createWebAppClient(clientId, clientSecret, clientHostName, clientPort, clientApp, directAccessGrantEnabled, null, multiTenancyApp);
}
private static ClientRepresentation createWebAppClient(String clientId, String clientSecret, String clientHostName, int clientPort,
- String clientApp, boolean directAccessGrantEnabled, String allowedOrigin) {
- return createWebAppClient(clientId, clientSecret, clientHostName, clientPort, clientApp, directAccessGrantEnabled, allowedOrigin, false);
- }
-
- private static ClientRepresentation createWebAppClient(String clientId, String clientSecret, String clientHostName, int clientPort,
- String clientApp, boolean directAccessGrantEnabled, String allowedOrigin, boolean multiTenancyApp) {
+ String clientApp, boolean directAccessGrantEnabled, String allowedOrigin, boolean multiTenancyApp) throws Exception {
ClientRepresentation client = new ClientRepresentation();
client.setClientId(clientId);
client.setPublicClient(false);
@@ -224,9 +247,29 @@ private static ClientRepresentation createWebAppClient(String clientId, String c
}
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();
+ File ksFile = new File(KEYSTORE_CLASSPATH + RSA_KEYSTORE_FILE_NAME);
+ if (ksFile.exists()) {
+ InputStream stream = findFile(KEYSTORE_CLASSPATH + RSA_KEYSTORE_FILE_NAME);
+ KeyStore keyStore = KeyStore.getInstance(PKCS12_KEYSTORE_TYPE);
+ keyStore.load(stream, KEYSTORE_PASS.toCharArray());
+ client.getAttributes().put("jwt.credential.certificate", Base64.getEncoder().encodeToString(keyStore.getCertificate(KEYSTORE_ALIAS).getEncoded()));
+ } else {
+ 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;
}
@@ -257,4 +300,12 @@ private static UserRepresentation createUser(String username, String password, L
return user;
}
+ private static InputStream findFile(String keystoreFile) {
+ try {
+ return new FileInputStream(keystoreFile);
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
}
\ No newline at end of file
diff --git a/http/oidc/src/test/java/org/wildfly/security/http/oidc/OidcBaseTest.java b/http/oidc/src/test/java/org/wildfly/security/http/oidc/OidcBaseTest.java
index de3115d96b..b604af8a8f 100644
--- a/http/oidc/src/test/java/org/wildfly/security/http/oidc/OidcBaseTest.java
+++ b/http/oidc/src/test/java/org/wildfly/security/http/oidc/OidcBaseTest.java
@@ -81,7 +81,7 @@
public class OidcBaseTest extends AbstractBaseHttpTest {
public static final String CLIENT_ID = "test-webapp";
- public static final String CLIENT_SECRET = "secret";
+ public static final String CLIENT_SECRET = "longerclientsecretthatisstleast256bitslong";
public static KeycloakContainer KEYCLOAK_CONTAINER;
public static final String TEST_REALM = "WildFly";
public static final String TEST_REALM_WITH_SCOPES = "WildFlyScopes";
@@ -100,6 +100,13 @@ public class OidcBaseTest extends AbstractBaseHttpTest {
public static final String TENANT2_ENDPOINT = "tenant2";
protected HttpServerAuthenticationMechanismFactory oidcFactory;
+ public enum RequestObjectErrorType {
+ INVALID_ALGORITHM,
+ MISSING_CLIENT_SECRET,
+ INVALID_REQUEST_FORMAT,
+ MISSING_ENC_VALUE
+ }
+
@AfterClass
public static void generalCleanup() throws Exception {
if (KEYCLOAK_CONTAINER != null) {
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 b7e1ce6ec6..fdda1aac44 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,6 +18,20 @@
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.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.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -32,6 +46,9 @@
import static org.wildfly.security.http.oidc.KeycloakConfiguration.TENANT2_USER;
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.AuthenticationRequestFormat.OAUTH2;
+import static org.wildfly.security.http.oidc.Oidc.AuthenticationRequestFormat.REQUEST;
+import static org.wildfly.security.http.oidc.Oidc.AuthenticationRequestFormat.REQUEST_URI;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
@@ -42,19 +59,18 @@
import javax.security.auth.callback.CallbackHandler;
-import org.apache.http.HttpStatus;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.wildfly.security.http.HttpServerAuthenticationMechanism;
-
+import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.TextPage;
import com.gargoylesoftware.htmlunit.WebClient;
-import com.gargoylesoftware.htmlunit.html.HtmlPage;
-
import io.restassured.RestAssured;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.QueueDispatcher;
+import org.apache.http.HttpStatus;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.wildfly.security.http.HttpServerAuthenticationMechanism;
/**
* Tests for the OpenID Connect authentication mechanism.
@@ -237,6 +253,100 @@ public void testOpenIDWithMultipleScopeValue() throws Exception {
true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT, expectedScope, false);
}
+ // 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 testSuccessfulOauth2Request() throws Exception {
+ performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(OAUTH2.getValue(), "", "", ""), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT);
+ }
+
+ @Test
+ public void testSuccessfulPlaintextRequest() throws Exception {
+ performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(REQUEST.getValue(), NONE, "", ""), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT);
+ }
+
+ @Test
+ public void testSuccessfulPlaintextEncryptedRequest() throws Exception {
+ performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(REQUEST.getValue(), NONE, RSA_OAEP, A128CBC_HS256), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT);
+ }
+
+ @Test
+ public void testSuccessfulRsaSignedAndEncryptedRequest() throws Exception {
+ performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(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 testSuccessfulPsSignedAndRsaEncryptedRequest() throws Exception {
+ performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(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 testInvalidSigningAlgorithm() throws Exception {
+ //ES256K is a valid signature algorithm, but not one of the ones supported by keycloak
+ testRequestObjectInvalidConfiguration(getOidcConfigurationInputStreamWithRequestParameter(REQUEST.getValue(), "ES256K", RSA_OAEP_256, A256CBC_HS512, KEYSTORE_CLASSPATH + KeycloakConfiguration.RSA_KEYSTORE_FILE_NAME, KeycloakConfiguration.KEYSTORE_ALIAS, PKCS12_KEYSTORE_TYPE), RequestObjectErrorType.INVALID_ALGORITHM);
+ }
+
+ @Test
+ public void testSuccessfulRsaSignedRequest() throws Exception {
+ performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(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 testSuccessfulPsSignedRequest() throws Exception {
+ performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(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 testInvalidRequestEncryptionAlgorithm() throws Exception {
+ // None is not a valid algorithm for encrypting jwt's and RSA-OAEP is not a valid algorithm for signing
+ testRequestObjectInvalidConfiguration(getOidcConfigurationInputStreamWithRequestParameter(REQUEST.getValue(), RSA1_5, NONE, NONE, KEYSTORE_CLASSPATH + KeycloakConfiguration.RSA_KEYSTORE_FILE_NAME, KeycloakConfiguration.KEYSTORE_ALIAS, PKCS12_KEYSTORE_TYPE), RequestObjectErrorType.INVALID_ALGORITHM);
+ }
+
+ @Test
+ public void testSuccessfulPlaintextRequestUri() throws Exception {
+ performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(REQUEST_URI.getValue(), NONE, "", ""), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT);
+ }
+
+ @Test
+ public void testSuccessfulHmacSignedRequestUri() throws Exception {
+ performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(REQUEST.getValue(), HMAC_SHA256, "", ""), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT);
+ }
+
+ @Test
+ public void testSuccessfulHmacSignedAndEncryptedRequestUri() throws Exception {
+ performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(REQUEST.getValue(), HMAC_SHA256, RSA_OAEP, A128CBC_HS256), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT);
+ }
+
+ @Test
+ public void testSuccessfulSignedAndEncryptedRequestUri() throws Exception {
+ performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(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);
+ }
+
+ @Test
+ public void testSuccessfulHmacSignedRequestObjectWithoutSecret() throws Exception {
+ // this is supposed to fail since for symmetric algorithms we sign the request object with the client secret
+ testRequestObjectInvalidConfiguration(getOidcConfigurationInputStreamWithRequestObjectPublicClient(REQUEST.getValue(), HMAC_SHA256), RequestObjectErrorType.MISSING_CLIENT_SECRET);
+ }
+
+ @Test
+ public void testIncorrectAuthenticationFormat() throws Exception {
+ testRequestObjectInvalidConfiguration(getOidcConfigurationInputStreamWithRequestObjectPublicClient("INVALID_REQUEST_PARAMETER", HMAC_SHA256), RequestObjectErrorType.INVALID_REQUEST_FORMAT);
+ }
+
+ @Test
+ public void testRequestObjectConfigMissingENCValue() throws Exception {
+ testRequestObjectInvalidConfiguration(getOidcConfigurationInputStreamWithoutEncValue(REQUEST.getValue(), RSA_OAEP), RequestObjectErrorType.MISSING_ENC_VALUE);
+ }
+
/*****************************************************************************************************************************************
* Tests for multi-tenancy.
*
@@ -496,6 +606,54 @@ private void performTenantRequest(String username, String password, String tenan
}
}
+ private void testRequestObjectInvalidConfiguration(InputStream oidcConfig, RequestObjectErrorType requestObjectErrorType) throws Exception {
+ try {
+ Map props = new HashMap<>();
+ try {
+ OidcClientConfiguration oidcClientConfiguration = OidcClientConfigurationBuilder.build(oidcConfig);
+ if (requestObjectErrorType == RequestObjectErrorType.MISSING_ENC_VALUE || requestObjectErrorType == RequestObjectErrorType.INVALID_REQUEST_FORMAT) {
+ Assert.fail("No error was thrown while attempting to build the client configuration.");
+ }
+ assertEquals(OidcClientConfiguration.RelativeUrlsUsed.NEVER, oidcClientConfiguration.getRelativeUrls());
+
+ OidcClientContext oidcClientContext = new OidcClientContext(oidcClientConfiguration);
+ oidcFactory = new OidcMechanismFactory(oidcClientContext);
+ HttpServerAuthenticationMechanism mechanism;
+
+ if (oidcClientConfiguration.getAuthenticationRequestFormat().contains(REQUEST.getValue())) {
+ mechanism = oidcFactory.createAuthenticationMechanism(OIDC_NAME, props, getCallbackHandler(true, "+phone+profile+email"));
+ } else {
+ mechanism = oidcFactory.createAuthenticationMechanism(OIDC_NAME, props, getCallbackHandler());
+ }
+
+ URI requestUri = new URI(getClientUrl());
+ TestingHttpServerRequest request = new TestingHttpServerRequest(null, requestUri);
+ try {
+ mechanism.evaluateRequest(request);
+ Assert.fail("No error was thrown while attempting to evaluate the request");
+ } catch (Exception e) {
+
+ if (requestObjectErrorType == RequestObjectErrorType.INVALID_ALGORITHM) {
+ assertTrue(e.getMessage().contains("Failed to create the authentication request"));
+ } else if (requestObjectErrorType == RequestObjectErrorType.MISSING_CLIENT_SECRET) {
+ assertTrue(e.getMessage().contains("The client secret has not been configured."));
+ } else {
+ throw e;
+ }
+ }
+ } catch (Exception e) {
+ if (requestObjectErrorType == RequestObjectErrorType.INVALID_REQUEST_FORMAT) {
+ assertTrue(e.getMessage().contains("Authentication request format must be one of the following: oauth2, request, request_uri."));
+ } else if (requestObjectErrorType == RequestObjectErrorType.MISSING_ENC_VALUE) {
+ assertTrue(e.getMessage().contains("Both request object encryption algorithm and request object content encryption algorithm must be configured to encrypt the request object."));
+ }
+ }
+ } finally {
+ client.setDispatcher(new QueueDispatcher());
+ }
+ }
+
+
private InputStream getOidcConfigurationInputStream() {
return getOidcConfigurationInputStream(CLIENT_SECRET);
}
@@ -582,7 +740,6 @@ private InputStream getOidcConfigurationInputStreamWithTokenSignatureAlgorithm()
"}";
return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8));
}
-
private InputStream getOidcConfigurationInputStreamWithScope(String scopeValue){
String oidcConfig = "{\n" +
" \"client-id\" : \"" + CLIENT_ID + "\",\n" +
@@ -590,6 +747,25 @@ private InputStream getOidcConfigurationInputStreamWithScope(String scopeValue){
" \"public-client\" : \"false\",\n" +
" \"scope\" : \"" + scopeValue + "\",\n" +
" \"ssl-required\" : \"EXTERNAL\",\n" +
+ " \"ssl-required\" : \"EXTERNAL\",\n" +
+ " \"credentials\" : {\n" +
+ " \"secret\" : \"" + CLIENT_SECRET + "\"\n" +
+ " }\n" +
+ "}";
+ return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8));
+ }
+ private InputStream getOidcConfigurationInputStreamWithRequestParameter(String requestParameter, String signingAlgorithm, String encryptionAlgorithm, String encMethod){
+ String oidcConfig = "{\n" +
+ " \"client-id\" : \"" + CLIENT_ID + "\",\n" +
+ " \"provider-url\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM_WITH_SCOPES + "/" + "\",\n" +
+ " \"public-client\" : \"false\",\n" +
+ " \"ssl-required\" : \"EXTERNAL\",\n" +
+ " \"ssl-required\" : \"EXTERNAL\",\n" +
+ " \"authentication-request-format\" : \"" + requestParameter + "\",\n" +
+ " \"request-object-signing-algorithm\" : \"" + signingAlgorithm + "\",\n" +
+ " \"request-object-encryption-alg-value\" : \"" + encryptionAlgorithm + "\",\n" +
+ " \"request-object-encryption-enc-value\" : \"" + encMethod + "\",\n" +
+ " \"scope\" : \"profile email phone\",\n" +
" \"credentials\" : {\n" +
" \"secret\" : \"" + CLIENT_SECRET + "\"\n" +
" }\n" +
@@ -597,6 +773,59 @@ private InputStream getOidcConfigurationInputStreamWithScope(String scopeValue){
return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8));
}
+ private InputStream getOidcConfigurationInputStreamWithoutEncValue(String requestParameter, String encryptionAlgorithm){
+ String oidcConfig = "{\n" +
+ " \"client-id\" : \"" + CLIENT_ID + "\",\n" +
+ " \"provider-url\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM_WITH_SCOPES + "/" + "\",\n" +
+ " \"public-client\" : \"false\",\n" +
+ " \"ssl-required\" : \"EXTERNAL\",\n" +
+ " \"ssl-required\" : \"EXTERNAL\",\n" +
+ " \"authentication-request-format\" : \"" + requestParameter + "\",\n" +
+ " \"request-object-encryption-alg-value\" : \"" + encryptionAlgorithm + "\",\n" +
+ " \"scope\" : \"profile email phone\",\n" +
+ " \"credentials\" : {\n" +
+ " \"secret\" : \"" + CLIENT_SECRET + "\"\n" +
+ " }\n" +
+ "}";
+ return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8));
+ }
+
+ private InputStream getOidcConfigurationInputStreamWithRequestParameter(String requestParameter, String signingAlgorithm, String encryptionAlgorithm, String encMethod, String keyStorePath, String alias, String keyStoreType){
+ String oidcConfig = "{\n" +
+ " \"client-id\" : \"" + CLIENT_ID + "\",\n" +
+ " \"provider-url\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM_WITH_SCOPES + "/" + "\",\n" +
+ " \"public-client\" : \"false\",\n" +
+ " \"ssl-required\" : \"EXTERNAL\",\n" +
+ " \"authentication-request-format\" : \"" + requestParameter + "\",\n" +
+ " \"request-object-signing-algorithm\" : \"" + signingAlgorithm + "\",\n" +
+ " \"request-object-encryption-alg-value\" : \"" + encryptionAlgorithm + "\",\n" +
+ " \"request-object-encryption-enc-value\" : \"" + 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" +
+ " \"scope\" : \"email phone profile\",\n" +
+ " \"credentials\" : {\n" +
+ " \"secret\" : \"" + CLIENT_SECRET + "\"\n" +
+ " }\n" +
+ "}";
+ return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8));
+ }
+
+ private InputStream getOidcConfigurationInputStreamWithRequestObjectPublicClient(String requestParameter, String signingAlgorithm){
+ String oidcConfig = "{\n" +
+ " \"client-id\" : \"" + CLIENT_ID + "\",\n" +
+ " \"provider-url\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM_WITH_SCOPES + "/" + "\",\n" +
+ " \"public-client\" : \"true\",\n" +
+ " \"ssl-required\" : \"EXTERNAL\",\n" +
+ " \"authentication-request-format\" : \"" + requestParameter + "\",\n" +
+ " \"request-object-signing-algorithm\" : \"" + signingAlgorithm + "\",\n" +
+ " \"scope\" : \"email phone profile\"\n" +
+ "}";
+ return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8));
+ }
+
private InputStream getOidcConfigurationInputStreamWithPrincipalAttribute(String principalAttributeValue) {
String oidcConfig = "{\n" +
" \"principal-attribute\" : \"" + principalAttributeValue + "\",\n" +
@@ -642,3 +871,4 @@ private static final String getClientPageTestForTenant(String tenant) {
return tenant.equals(TENANT1_ENDPOINT) ? TENANT1_ENDPOINT : TENANT2_ENDPOINT + ":" + CLIENT_PAGE_TEXT;
}
}
+
diff --git a/pom.xml b/pom.xml
index 20543860b1..e71dc5e2d3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -99,6 +99,7 @@
4.3.3
2.40.0
2.3.0
+ 3.1.0.Final
INFO
@@ -1152,6 +1153,12 @@
${version.org.bouncycastle}
test
+
+ org.keycloak
+ keycloak-services
+ ${version.org.keycloak.keycloak-services}
+ test
+