diff --git a/http/oidc/pom.xml b/http/oidc/pom.xml
index be137f0e480..5e2acdd3a01 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,11 @@
jmockit
test
+
+ org.wildfly.security
+ wildfly-elytron-credential-source-impl
+ 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..0a1e42b96d7 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
@@ -233,5 +233,8 @@ 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();
+
}
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..5f15eeeefd9 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
@@ -156,7 +156,7 @@ protected JwtClaims createRequestToken(String clientId, String tokenUrl) {
return jwtClaims;
}
- private static KeyPair loadKeyPairFromKeyStore(String keyStoreFile, String storePassword, String keyPassword, String keyAlias, String keyStoreType) {
+ protected static KeyPair loadKeyPairFromKeyStore(String keyStoreFile, String storePassword, String keyPassword, String keyAlias, String keyStoreType) {
InputStream stream = findFile(keyStoreFile);
try {
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
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..68561407e3d 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
@@ -52,10 +52,14 @@ public class Oidc {
public static final String TEXT_CONTENT_TYPE = "text/*";
public static final String DISCOVERY_PATH = ".well-known/openid-configuration";
public static final String KEYCLOAK_REALMS_PATH = "realms/";
+ public static final String KEYSTORE_PASS = "password";
+ public static final String PKCS12_KEYSTORE_TYPE = "PKCS12";
public static final String JSON_CONFIG_CONTEXT_PARAM = "org.wildfly.security.http.oidc.json.config";
static final String ACCOUNT_PATH = "account";
public static final String CLIENTS_MANAGEMENT_REGISTER_NODE_PATH = "clients-managements/register-node";
public static final String CLIENTS_MANAGEMENT_UNREGISTER_NODE_PATH = "clients-managements/unregister-node";
+ public static final String ADMIN_CONSOLE_PATH = "admin/master/console/#";
+ public static final String REALM_SETTING_KEYS_PATH = "realm-settings/keys";
public static final String SLASH = "/";
public static final String OIDC_CLIENT_CONTEXT_KEY = OidcClientContext.class.getName();
public static final String CLIENT_ID = "client_id";
@@ -73,12 +77,17 @@ 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";
public static final String OIDC_SCOPE = "openid";
public static final String REDIRECT_URI = "redirect_uri";
public static final String REFRESH_TOKEN = "refresh_token";
+ public static final String REQUEST_TYPE_OAUTH2 = "oauth2";
+ public static final String REQUEST_TYPE_REQUEST = "request";
+ public static final String REQUEST_TYPE_REQUEST_URI = "request_uri";
public static final String RESPONSE_TYPE = "response_type";
public static final String SESSION_STATE = "session_state";
public static final String SOAP_ACTION = "SOAPAction";
@@ -116,6 +125,35 @@ public class Oidc {
public static final String X_REQUESTED_WITH = "X-Requested-With";
public static final String XML_HTTP_REQUEST = "XMLHttpRequest";
+ /* Accepted Request Object Signing Algorithms for KeyCloak*/
+ public static final String NONE = "none";
+ public static final String RS_256 = "RS256";
+ public static final String HS_256 = "HS256";
+ public static final String HS_384 = "HS384";
+ public static final String HS_512 = "HS512";
+ public static final String ES_256 = "ES256";
+ public static final String ES_384 = "ES384";
+ public static final String ES_512 = "ES512";
+ public static final String ES_256K = "ES256K";
+ public static final String RS_384 = "RS384";
+ public static final String RS_512 = "RS512";
+ public static final String PS_256 = "PS256";
+ public static final String PS_384 = "PS384";
+ public static final String PS_512 = "PS512";
+
+ /* 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 A256GCM = "A256GCM";
+ public static final String A192GCM = "A192GCM";
+ public static final String A128GCM = "A128GCM";
+ public static final String A128CBC_HS256 = "A128CBC-HS256";
+ public static final String A192CBC_HS384 = "A192CBC-HS384";
+ public static final String A256CBC_HS512 = "A256CBC-HS512";
+
/**
* Bearer token pattern.
* The Bearer token authorization header is of the form "Bearer", followed by optional whitespace, followed by
@@ -276,6 +314,8 @@ public static String getJavaAlgorithm(String algorithm) {
return ES384;
case AlgorithmIdentifiers.ECDSA_USING_P521_CURVE_AND_SHA512:
return ES512;
+ case AlgorithmIdentifiers.NONE:
+ return NONE;
default:
throw log.unknownAlgorithm(algorithm);
}
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 db872b30a89..ce99f2ddad7 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,16 @@
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.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
import java.net.URI;
+import java.nio.charset.StandardCharsets;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
@@ -41,7 +48,7 @@
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;
+import org.jose4j.jwk.JsonWebKeySet;
/**
* The OpenID Connect (OIDC) configuration for a client application. This class is based on
@@ -80,7 +87,13 @@ public enum RelativeUrlsUsed {
protected String unregisterNodeUrl;
protected String jwksUrl;
protected String issuerUrl;
+ protected String authorizationEndpoint;
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 +139,19 @@ 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 requestEncryptEncValue;
+ protected String pushedAuthorizationRequestEndpoint;
+ protected String clientKeyStoreFile;
+ protected String clientKeyStorePass;
+ protected String clientKeyPass;
+ protected String clientKeyAlias;
+ protected String clientKeystoreType;
+
+ protected String realmKey;
+ protected JsonWebKeySet realmKeySet = new JsonWebKeySet();
public OidcClientConfiguration() {
}
@@ -223,6 +249,15 @@ protected void resolveUrls() {
tokenUrl = config.getTokenEndpoint();
logoutUrl = config.getLogoutEndpoint();
jwksUrl = config.getJwksUri();
+ authorizationEndpoint = config.getAuthorizationEndpoint();
+ requestParameterSupported = config.getRequestParameterSupported();
+ requestObjectSigningAlgValuesSupported = config.getRequestObjectSigningAlgValuesSupported();
+ requestObjectEncryptionEncValuesSupported = config.getRequestObjectEncryptionEncValuesSupported();
+ requestObjectEncryptionAlgValuesSupported = config.getRequestObjectEncryptionAlgValuesSupported();
+ requestUriParameterSupported = config.getRequestUriParameterSupported();
+ realmKeySet = createrealmKeySet();
+ pushedAuthorizationRequestEndpoint = config.getPushedAuthorizationRequestEndpoint();
+
if (authServerBaseUrl != null) {
// keycloak-specific properties
accountUrl = getUrl(issuerUrl, ACCOUNT_PATH);
@@ -237,6 +272,26 @@ protected void resolveUrls() {
}
}
+ private JsonWebKeySet createrealmKeySet() throws Exception{
+ HttpGet request = new HttpGet(jwksUrl);
+ request.addHeader(ACCEPT, JSON_CONTENT_TYPE);
+ HttpResponse response = getClient().execute(request);
+ if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
+ EntityUtils.consumeQuietly(response.getEntity());
+ throw new Exception(response.getStatusLine().getReasonPhrase());
+ }
+ 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);
+ }
+ }
+ return new JsonWebKeySet(textBuilder.toString());
+ }
+
protected OidcProviderMetadata getOidcProviderMetadata(String discoveryUrl) throws Exception {
HttpGet request = new HttpGet(discoveryUrl);
request.addHeader(ACCEPT, JSON_CONTENT_TYPE);
@@ -246,7 +301,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 +384,22 @@ public String getIssuerUrl() {
return issuerUrl;
}
+ public List getRequestObjectSigningAlgValuesSupported() {
+ return requestObjectSigningAlgValuesSupported;
+ }
+
+ public boolean getRequestParameterSupported() {
+ return requestParameterSupported;
+ }
+
+ public boolean getRequestUriParameterSupported() {
+ return requestUriParameterSupported;
+ }
+
+ public String getAuthorizationEndpoint() {
+ return authorizationEndpoint;
+ }
+
public void setResource(String resource) {
this.resource = resource;
}
@@ -651,4 +722,99 @@ public String getTokenSignatureAlgorithm() {
return tokenSignatureAlgorithm;
}
+ public String getAuthenticationRequestFormat() {
+ return authenticationRequestFormat;
+ }
+
+ public void setAuthenticationRequestFormat(String requestObjectType ) {
+ this.authenticationRequestFormat = requestObjectType;
+ }
+
+ public String getRequestSignatureAlgorithm() {
+ return requestSignatureAlgorithm;
+ }
+
+ public void setRequestSignatureAlgorithm(String algorithm) {
+ this.requestSignatureAlgorithm = algorithm;
+ }
+
+ public String getRequestEncryptAlgorithm() {
+ return requestEncryptAlgorithm;
+ }
+
+ public void setRequestEncryptAlgorithm(String algorithm) {
+ this.requestEncryptAlgorithm = algorithm;
+ }
+
+ public String getRequestEncryptEncValue() {
+ return requestEncryptEncValue;
+ }
+
+ public void setRequestEncryptEncValue (String enc) {
+ this.requestEncryptEncValue = enc;
+ }
+
+ public String getRealmKey () {
+ return realmKey;
+ }
+
+ public void setRealmKey(String key) {
+ this.realmKey = key;
+ }
+
+ public String getClientKeyStoreFile () {
+ return clientKeyStoreFile;
+ }
+
+ public void setClientKeyStoreFile(String keyStoreFile) {
+ this.clientKeyStoreFile = keyStoreFile;
+ }
+
+ public String getClientKeyStorePassword () {
+ return clientKeyStorePass;
+ }
+
+ public void setClientKeyStorePassword(String pass) {
+ this.clientKeyStorePass = pass;
+ }
+
+ public String getClientKeyPassword () {
+ return clientKeyPass;
+ }
+
+ public void setClientKeyPassword(String pass) {
+ this.clientKeyPass = pass;
+ }
+
+ public String getClientKeystoreType() {
+ return clientKeystoreType;
+ }
+
+ public void setClientKeystoreType(String type) {
+ this.clientKeystoreType = type;
+ }
+
+ public String getClientKeyAlias() {
+ return clientKeyAlias;
+ }
+
+ public void setClientKeyAlias(String alias) {
+ this.clientKeyAlias = alias;
+ }
+
+ public JsonWebKeySet getrealmKeySet() {
+ return realmKeySet;
+ }
+
+ public void setrealmKeySet(JsonWebKeySet keySet) {
+ this.realmKeySet = keySet;
+ }
+
+ 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..217700f18ae 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.wildfly.security.http.oidc.Oidc.NONE;
+import static org.wildfly.security.http.oidc.Oidc.REQUEST_TYPE_OAUTH2;
import static org.wildfly.security.http.oidc.Oidc.SSLRequired;
import static org.wildfly.security.http.oidc.Oidc.TokenStore;
@@ -100,6 +102,33 @@ 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);
+ }
+ if (oidcJsonConfiguration.getRequestSignatureAlgorithm() != null) {
+ oidcClientConfiguration.setRequestSignatureAlgorithm(oidcJsonConfiguration.getRequestSignatureAlgorithm());
+ } else {
+ oidcClientConfiguration.setRequestSignatureAlgorithm(NONE);
+ }
+ if (oidcJsonConfiguration.getRequestEncryptAlgorithm() != null && oidcJsonConfiguration.getRequestEncryptEncValue() != null) { //both are required to encrypt the request object
+ oidcClientConfiguration.setRequestEncryptAlgorithm(oidcJsonConfiguration.getRequestEncryptAlgorithm());
+ oidcClientConfiguration.setRequestEncryptEncValue(oidcJsonConfiguration.getRequestEncryptEncValue());
+ }
+ if (oidcJsonConfiguration.getClientKeystoreFile() != null && oidcJsonConfiguration.getClientKeystorePassword() != null &&
+ oidcJsonConfiguration.getClientKeyPassword() != null && oidcJsonConfiguration.getClientKeyAlias() != null) {
+ oidcClientConfiguration.setClientKeyStoreFile(oidcJsonConfiguration.getClientKeystoreFile());
+ oidcClientConfiguration.setClientKeyStorePassword(oidcJsonConfiguration.getClientKeystorePassword());
+ oidcClientConfiguration.setClientKeyPassword(oidcJsonConfiguration.getClientKeyPassword());
+ oidcClientConfiguration.setClientKeyAlias(oidcJsonConfiguration.getClientKeyAlias());
+ if (oidcJsonConfiguration.getClientKeystoreType() != null) {
+ oidcClientConfiguration.setClientKeystoreType(oidcJsonConfiguration.getClientKeystoreType());
+ }
+ }
+ if (oidcJsonConfiguration.getRealmKey() != null) {
+ oidcClientConfiguration.setRealmKey(oidcJsonConfiguration.getRealmKey());
+ }
if (oidcJsonConfiguration.getPrincipalAttribute() != null) oidcClientConfiguration.setPrincipalAttribute(oidcJsonConfiguration.getPrincipalAttribute());
oidcClientConfiguration.setResourceCredentials(oidcJsonConfiguration.getCredentials());
@@ -190,7 +219,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..021346fa155 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
@@ -28,6 +28,7 @@
import org.apache.http.client.HttpClient;
import org.apache.http.client.utils.URIBuilder;
+import org.jose4j.jwk.JsonWebKeySet;
/**
*
@@ -525,6 +526,97 @@ 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 getRequestEncryptEncValue() {
+ return delegate.requestEncryptEncValue;
+ }
+
+ @Override
+ public void setRequestEncryptEncValue (String enc) {
+ delegate.requestEncryptEncValue = enc;
+ }
+
+ @Override
+ public String getRealmKey () {
+ return delegate.realmKey;
+ }
+
+ @Override
+ public void setRealmKey(String key) {
+ delegate.realmKey = key;
+ }
+
+ @Override
+ public JsonWebKeySet getrealmKeySet() {
+ return delegate.realmKeySet;
+ }
+
+ @Override
+ public String getClientKeystoreType() {
+ return delegate.clientKeystoreType;
+ }
+
+ @Override
+ public void setClientKeystoreType(String type) {
+ delegate.clientKeystoreType = type;
+ }
+
+ @Override
+ public String getClientKeyAlias() {
+ return delegate.clientKeyAlias;
+ }
+
+ @Override
+ public void setClientKeyAlias(String alias) {
+ delegate.clientKeyAlias = alias;
+ }
+
+ @Override
+ public void setrealmKeySet(JsonWebKeySet keySet) {
+ delegate.realmKeySet = keySet;
+ }
+
+ @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..08164bcf54a 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
@@ -41,12 +41,13 @@
"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",
+ "client-keystore", "client-keystore-file", "client-keystore-password", "client-key-password", "client-key-alias", "client-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"
+ "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 {
@@ -60,10 +61,16 @@ public class OidcJsonConfiguration {
protected String truststorePassword;
@JsonProperty("client-keystore")
protected String clientKeystore;
+ @JsonProperty("client-keystore-file")
+ protected String clientKeystoreFile;
@JsonProperty("client-keystore-password")
protected String clientKeystorePassword;
@JsonProperty("client-key-password")
protected String clientKeyPassword;
+ @JsonProperty("client-key-alias")
+ protected String clientKeyAlias;
+ @JsonProperty("client-keystore-type")
+ protected String clientKeystoreType;
@JsonProperty("connection-pool-size")
protected int connectionPoolSize = 20;
@JsonProperty("always-refresh-token")
@@ -140,6 +147,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 requestEncryptEncValue;
+
/**
* The Proxy url to use for requests to the auth-server, configurable via the adapter config property {@code proxy-url}.
*/
@@ -178,6 +197,13 @@ public void setTruststorePassword(String truststorePassword) {
this.truststorePassword = truststorePassword;
}
+ public String getClientKeystoreFile() {
+ return clientKeystoreFile;
+ }
+
+ public void setClientKeystoreFile(String clientKeystoreFile) {
+ this.clientKeystoreFile = clientKeystoreFile;
+ }
public String getClientKeystore() {
return clientKeystore;
}
@@ -186,6 +212,22 @@ public void setClientKeystore(String clientKeystore) {
this.clientKeystore = clientKeystore;
}
+ public String getClientKeystoreType() {
+ return clientKeystoreType;
+ }
+
+ public void setClientKeystoreType(String type) {
+ this.clientKeystoreType = type;
+ }
+
+ public String getClientKeyAlias() {
+ return clientKeyAlias;
+ }
+
+ public void setClientKeyAlias(String alias) {
+ this.clientKeyAlias = alias;
+ }
+
public String getClientKeystorePassword() {
return clientKeystorePassword;
}
@@ -511,5 +553,36 @@ public void setTokenSignatureAlgorithm(String tokenSignatureAlgorithm) {
this.tokenSignatureAlgorithm = tokenSignatureAlgorithm;
}
+ public String getAuthenticationRequestFormat() {
+ return authenticationRequestFormat;
+ }
+
+ public void setAuthenticationRequestFormat(String requestType) {
+ this.authenticationRequestFormat = requestType;
+ }
+
+ public String getRequestSignatureAlgorithm() {
+ return requestSignatureAlgorithm;
+ }
+
+ public void setRequestSignatureAlgorithm(String algorithm) {
+ this.requestSignatureAlgorithm = algorithm;
+ }
+
+ public String getRequestEncryptAlgorithm() {
+ return requestEncryptAlgorithm;
+ }
+
+ public void setRequestEncryptAlgorithm(String algorithm) {
+ this.requestEncryptAlgorithm = algorithm;
+ }
+
+ public String getRequestEncryptEncValue() {
+ return requestEncryptEncValue;
+ }
+
+ public void setRequestEncryptEncValue (String enc) {
+ this.requestEncryptEncValue = enc;
+ }
}
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..3f775251cb0 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 6b51d980d97..b7132335ec6 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,42 +18,78 @@
package org.wildfly.security.http.oidc;
+import static org.jose4j.jwa.AlgorithmConstraints.ConstraintType.PERMIT;
import static org.wildfly.security.http.oidc.ElytronMessages.log;
+import static org.wildfly.security.http.oidc.JWTClientCredentialsProvider.loadKeyPairFromKeyStore;
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;
import static org.wildfly.security.http.oidc.Oidc.ERROR;
+import static org.wildfly.security.http.oidc.Oidc.HS_256;
+import static org.wildfly.security.http.oidc.Oidc.HS_384;
+import static org.wildfly.security.http.oidc.Oidc.HS_512;
import static org.wildfly.security.http.oidc.Oidc.KC_IDP_HINT;
import static org.wildfly.security.http.oidc.Oidc.LOGIN_HINT;
import static org.wildfly.security.http.oidc.Oidc.MAX_AGE;
+import static org.wildfly.security.http.oidc.Oidc.NONE;
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_TYPE_OAUTH2;
+import static org.wildfly.security.http.oidc.Oidc.REQUEST_TYPE_REQUEST;
+import static org.wildfly.security.http.oidc.Oidc.REQUEST_TYPE_REQUEST_URI;
+import static org.wildfly.security.http.oidc.Oidc.REQUEST_URI;
+import static org.wildfly.security.http.oidc.Oidc.RSA_OAEP;
+import static org.wildfly.security.http.oidc.Oidc.RSA_OAEP_256;
+import static org.wildfly.security.http.oidc.Oidc.RSA1_5;
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.jwk.JsonWebKey;
+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;
+
/**
* @author Bill Burke
* @author Farah Juma
@@ -180,15 +216,47 @@ 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());
+ if (deployment.getAuthenticationRequestFormat().contains(REQUEST_TYPE_OAUTH2)) {
+ redirectUriBuilder.addParameter(REDIRECT_URI, redirectUri)
+ .addParameter(STATE, state)
+ .addParameters(forwardedQueryParams);
+ } else if (deployment.getAuthenticationRequestFormat().contains(REQUEST_TYPE_REQUEST_URI)) {
+ if (deployment.getRequestUriParameterSupported()) {
+ String request = convertToRequestParameter(redirectUriBuilder, redirectUri, state, forwardedQueryParams);
+ String request_uri = getRequestUri(request);
+ redirectUriBuilder.addParameter(REQUEST_URI, request_uri);
+ redirectUriBuilder.addParameter(REDIRECT_URI, redirectUri);
+ } else {
+ // send request as usual
+ redirectUriBuilder.addParameter(REDIRECT_URI, redirectUri)
+ .addParameter(STATE, state)
+ .addParameters(forwardedQueryParams);
+ log.info("The OpenID provider does not support the request parameter. Sending the request using OAuth2 format.");
+ }
+ } else if (deployment.getAuthenticationRequestFormat().contains(REQUEST_TYPE_REQUEST)) {
+ if (deployment.getRequestParameterSupported()) {
+ // add request objects into request parameter
+ String request = convertToRequestParameter(redirectUriBuilder, redirectUri, state, forwardedQueryParams);
+ redirectUriBuilder.addParameter(REDIRECT_URI, redirectUri);
+ redirectUriBuilder.addParameter(REQUEST, request);
+ } else {
+ // send request as usual
+ redirectUriBuilder.addParameter(REDIRECT_URI, redirectUri)
+ .addParameter(STATE, state)
+ .addParameters(forwardedQueryParams);
+ log.info("The OpenID provider does not support the request_uri parameter. Sending the request using OAuth2 format.");
+ }
+ }
return redirectUriBuilder.build().toString();
} catch (URISyntaxException e) {
throw log.unableToCreateRedirectResponse(e);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
}
}
@@ -416,4 +484,112 @@ private static boolean hasScope(String scopeParam, String targetScope) {
}
return false;
}
+ private String convertToRequestParameter(URIBuilder redirectUriBuilder, String redirectUri, String state, List forwardedQueryParams) throws Exception {
+ redirectUriBuilder.addParameter(SCOPE, OIDC_SCOPE);
+ forwardedQueryParams.add(new BasicNameValuePair(STATE, state));
+ forwardedQueryParams.add(new BasicNameValuePair(REDIRECT_URI, redirectUri));
+ forwardedQueryParams.add(new BasicNameValuePair(RESPONSE_TYPE, CODE));
+ forwardedQueryParams.add(new BasicNameValuePair(CLIENT_ID, deployment.getResourceName()));
+
+ JwtClaims jwtClaims = new JwtClaims();
+ jwtClaims.setIssuer(deployment.getIssuerUrl());
+ jwtClaims.setAudience(deployment.getProviderUrl());
+ for ( NameValuePair parameter: forwardedQueryParams) {
+ jwtClaims.setClaim(parameter.getName(), parameter.getValue());
+ }
+
+ // sign JWT first before encrypting
+ JsonWebSignature signedRequest = signRequest(jwtClaims);
+
+ // Encrypting optional
+ if (deployment.getRequestEncryptAlgorithm() != null && !deployment.getRequestEncryptAlgorithm().isEmpty() &&
+ deployment.getRequestEncryptEncValue() != null && !deployment.getRequestEncryptEncValue().isEmpty()) {
+ return encryptRequest(signedRequest).getCompactSerialization();
+ } else {
+ return signedRequest.getCompactSerialization();
+ }
+ }
+
+ public KeyPair getkeyPair() throws IOException {
+ if (deployment.getClientKeyStoreFile().contains(PROTOCOL_CLASSPATH)) {
+ deployment.setClientKeyStoreFile(deployment.getClientKeyStoreFile().replace(PROTOCOL_CLASSPATH, Objects.requireNonNull(Thread.currentThread().getContextClassLoader().getResource("")).getPath()));
+ }
+ if (!deployment.getRequestSignatureAlgorithm().equals(NONE) && deployment.getClientKeyStoreFile() == null){
+ throw log.invalidKeyStoreConfiguration();
+ } else {
+ return loadKeyPairFromKeyStore(deployment.getClientKeyStoreFile(),
+ deployment.getClientKeyStorePassword(), deployment.getClientKeyPassword(),
+ deployment.getClientKeyAlias(), deployment.getClientKeystoreType());
+ }
+ }
+
+ public JsonWebSignature signRequest(JwtClaims jwtClaims) throws Exception {
+ JsonWebSignature jsonWebSignature = new JsonWebSignature();
+ jsonWebSignature.setPayload(jwtClaims.toJson());
+
+ if (deployment.getRequestSignatureAlgorithm().contains(NONE)) { //unsigned
+ jsonWebSignature.setAlgorithmConstraints(AlgorithmConstraints.NO_CONSTRAINTS);
+ jsonWebSignature.setAlgorithmHeaderValue(NONE);
+ } else if (deployment.getRequestSignatureAlgorithm().contains(HS_256)
+ || deployment.getRequestSignatureAlgorithm().contains(HS_384)
+ || deployment.getRequestSignatureAlgorithm().contains(HS_512)) { //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.setAlgorithmConstraints(new AlgorithmConstraints(PERMIT, deployment.getRequestSignatureAlgorithm()));
+ jsonWebSignature.setAlgorithmHeaderValue(deployment.getRequestSignatureAlgorithm());
+ }
+ jsonWebSignature.sign();
+ return jsonWebSignature;
+ }
+
+ private JsonWebEncryption encryptRequest(JsonWebSignature signedRequest) throws JoseException {
+ JsonWebEncryption jsonEncryption = new JsonWebEncryption();
+ jsonEncryption.setPayload(signedRequest.getCompactSerialization());
+ jsonEncryption.setAlgorithmConstraints(new AlgorithmConstraints(AlgorithmConstraints.ConstraintType.PERMIT, deployment.getRequestEncryptAlgorithm(), deployment.getRequestEncryptEncValue()));
+ jsonEncryption.setAlgorithmHeaderValue(deployment.getRequestEncryptAlgorithm());
+ jsonEncryption.setEncryptionMethodHeaderParameter(deployment.getRequestEncryptEncValue());
+ List jwkList = deployment.getrealmKeySet().getJsonWebKeys();
+ jsonEncryption.setDoKeyValidation(false);
+ for (JsonWebKey jwk : jwkList) {
+ if (deployment.getRequestEncryptAlgorithm().contains(RSA_OAEP) || deployment.getRequestEncryptAlgorithm().contains(RSA_OAEP_256) || deployment.getRequestEncryptAlgorithm().contains(RSA1_5)) {
+ if (jwk.getUse().equals("enc")) { //JWT's for keycloak are to be encrypted with realm public keys
+ jsonEncryption.setKey(jwk.getKey());
+ break;
+ }
+ }
+ }
+ return jsonEncryption;
+ }
+
+ private String getRequestUri(String request) throws Exception {
+ 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 new Exception(response.getStatusLine().getReasonPhrase());
+ }
+ 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..3b46610803e 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,21 +18,46 @@
package org.wildfly.security.http.oidc;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Base64;
import java.util.Collections;
+import java.util.Date;
import java.util.List;
+import java.util.Objects;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.jose4j.lang.JoseException;
+import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
import org.keycloak.representations.AccessTokenResponse;
+import org.keycloak.representations.KeyStoreConfig;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.RolesRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
-
import io.restassured.RestAssured;
+import static org.wildfly.security.http.oidc.Oidc.KEYSTORE_PASS;
+
/**
* Keycloak configuration for testing.
*
@@ -47,6 +72,11 @@ 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 String KEYSTORE_CLASSPATH;
/**
* Configure RealmRepresentation as follows:
@@ -60,14 +90,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 JoseException, GeneralSecurityException, IOException, OperatorCreationException {
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 JoseException, GeneralSecurityException, IOException, OperatorCreationException {
return createRealm(realmName, clientId, clientSecret, clientHostName, clientPort, clientApp, directAccessGrantEnabled, bearerOnlyClientId, corsClientId);
}
@@ -102,14 +132,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 JoseException, GeneralSecurityException, IOException, OperatorCreationException {
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 GeneralSecurityException, IOException, OperatorCreationException {
RealmRepresentation realm = new RealmRepresentation();
realm.setRealm(name);
@@ -140,15 +170,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 GeneralSecurityException, IOException, OperatorCreationException {
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 GeneralSecurityException, IOException, OperatorCreationException {
ClientRepresentation client = new ClientRepresentation();
client.setClientId(clientId);
client.setPublicClient(false);
@@ -157,12 +188,60 @@ 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();
+ String rsaCert = generateKeyStoreFileAndGetCertificate("Rsa", 2048, KEYSTORE_CLASSPATH + RSA_KEYSTORE_FILE_NAME, "SHA256WITHRSA");
+ client.getAttributes().put("jwt.credential.certificate", rsaCert);
return client;
}
+ static String generateKeyStoreFileAndGetCertificate(String algorithm, int keySize, String keystorePath, String certSignAlg) throws GeneralSecurityException, IOException, OperatorCreationException {
+ KeyPairGenerator keyGen = KeyPairGenerator.getInstance(algorithm);
+ keyGen.initialize(keySize);
+ KeyPair keys = keyGen.generateKeyPair();
+ X509Certificate cert = createCertificate(keys, certSignAlg);
+ KeyStore keystore = createKeyStore(cert, keys.getPrivate());
+ try (FileOutputStream fileOutputStream = new FileOutputStream(keystorePath)) {
+ keystore.store(fileOutputStream, KEYSTORE_PASS.toCharArray());
+ }
+
+ KeyStoreConfig keyStoreConfig = new KeyStoreConfig();
+ keyStoreConfig.setKeyAlias(KEYSTORE_ALIAS);
+ keyStoreConfig.setStorePassword(KEYSTORE_PASS);
+ keyStoreConfig.setKeyPassword(KEYSTORE_PASS);
+ keyStoreConfig.setRealmCertificate(true);
+ return Base64.getEncoder().encodeToString(cert.getEncoded());
+ }
+
+ private static KeyStore createKeyStore(X509Certificate certificate, PrivateKey privateKey) throws IOException, GeneralSecurityException {
+ KeyStore keyStore = createEmptyKeyStore();
+ keyStore.setCertificateEntry("jwtKeystore", certificate);
+ keyStore.setKeyEntry("jwtKeystore", privateKey, "password".toCharArray(), new Certificate[]{certificate});
+ return keyStore;
+ }
+
+ private static KeyStore createEmptyKeyStore() throws IOException, GeneralSecurityException {
+ KeyStore keyStore = KeyStore.getInstance("PKCS12");
+ keyStore.load(null,null);
+ return keyStore;
+ }
+
+ private static X509Certificate createCertificate(KeyPair keyPair, String certSignAlg) throws GeneralSecurityException, OperatorCreationException {
+ X500Name subject = new X500Name("cn=localhost");
+ Date startDate = new Date(System.currentTimeMillis() - 24 * 60 * 60 * 1000); //1 day before now
+ Date endDate = new Date(System.currentTimeMillis() + (long) 2 * 365 * 24 * 60 * 60 * 1000); //2 years from now
+ X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(subject, BigInteger.valueOf(System.currentTimeMillis()), startDate, endDate, subject, keyPair.getPublic());
+ final ContentSigner contentSigner = new JcaContentSignerBuilder(certSignAlg).build(keyPair.getPrivate());
+
+ return new JcaX509CertificateConverter().setProvider(new BouncyCastleProvider())
+ .getCertificate(certGen.build(contentSigner));
+ }
+
private static ClientRepresentation createBearerOnlyClient(String clientId) {
ClientRepresentation client = new ClientRepresentation();
client.setClientId(clientId);
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 84132472d1c..00f05255bfa 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
@@ -21,7 +21,26 @@
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.KeycloakConfiguration.KEYSTORE_CLASSPATH;
+import static org.wildfly.security.http.oidc.Oidc.HS_256;
+import static org.wildfly.security.http.oidc.Oidc.HS_512;
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.REQUEST_TYPE_OAUTH2;
+import static org.wildfly.security.http.oidc.Oidc.REQUEST_TYPE_REQUEST;
+import static org.wildfly.security.http.oidc.Oidc.REQUEST_TYPE_REQUEST_URI;
+import static org.wildfly.security.http.oidc.Oidc.NONE;
+import static org.wildfly.security.http.oidc.Oidc.RSA1_5;
+import static org.wildfly.security.http.oidc.Oidc.RSA_OAEP;
+import static org.wildfly.security.http.oidc.Oidc.RSA_OAEP_256;
+import static org.wildfly.security.http.oidc.Oidc.A128CBC_HS256;
+import static org.wildfly.security.http.oidc.Oidc.A192CBC_HS384;
+import static org.wildfly.security.http.oidc.Oidc.A256CBC_HS512;
+import static org.wildfly.security.http.oidc.Oidc.RS_256;
+import static org.wildfly.security.http.oidc.Oidc.RS_512;
+import static org.wildfly.security.http.oidc.Oidc.PS_256;
+import static org.wildfly.security.http.oidc.Oidc.KEYSTORE_PASS;
+import static org.wildfly.security.http.oidc.Oidc.PKCS12_KEYSTORE_TYPE;
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.
@@ -162,8 +181,102 @@ public void testTokenSignatureAlgorithm() throws Exception {
true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT);
}
+ @Test
+ public void testOpenIDWithOauth2Request() throws Exception {
+ performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(REQUEST_TYPE_OAUTH2, "", "", ""), 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, 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, 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, RS_512, 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, PS_256, 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, "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, RS_256, "", "", 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, PS_256, "", "", 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, 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, 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, HS_256, "", ""), 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, HS_512, 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, RS_256, 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);
@@ -175,10 +288,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("org.jose4j.lang.InvalidAlgorithmException"));
+ 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)) {
+ assertTrue(response.getFirstResponseHeaderValue("Location").contains("scope=" + OIDC_SCOPE));
+ assertTrue(response.getFirstResponseHeaderValue("Location").contains("request_uri="));
+ } else if (oidcClientConfiguration.getAuthenticationRequestFormat().contains(REQUEST_TYPE_REQUEST)) {
+ assertTrue(response.getFirstResponseHeaderValue("Location").contains("scope=" + OIDC_SCOPE));
+ assertTrue(response.getFirstResponseHeaderValue("Location").contains("request="));
+ }
if (loginToKeycloak) {
client.setDispatcher(createAppResponse(mechanism, expectedDispatcherStatusCode, expectedLocation, clientPageText));
@@ -290,4 +422,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" +
+ " \"client-keystore-file\" : \"" + keyStorePath + "\",\n" +
+ " \"client-keystore-type\" : \"" + keyStoreType + "\",\n" +
+ " \"client-keystore-password\" : \"" + KEYSTORE_PASS + "\",\n" +
+ " \"client-key-password\" : \"" + KEYSTORE_PASS + "\",\n" +
+ " \"client-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 e2e11e973d9..40ded62441c 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
+