options = new HashMap<>();
- if (debug) {
- options.put("debug", "true");
- }
- options.put("principal", principal);
-
- options.put("storeKey", "true");
- options.put("useKeyTab", "true");
- if (keyTab != null) options.put("keyTab", keyTab.getAbsolutePath());
- options.put("isInitiator", (isServer && !obtainKerberosTicket) ? "false" : "true");
-
- if (this.options != null) {
- options.putAll(this.options);
- }
-
- log.tracef("Created LoginContext configuration: %s", options.toString());
-
- final AppConfigurationEntry[] aceArray = new AppConfigurationEntry[] {
- new AppConfigurationEntry(KRB5LoginModule, REQUIRED, options)
- };
-
- return new Configuration() {
-
- @Override
- public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
- assert "KDC".equals(name);
- return aceArray;
- }
-
- };
- }
-
- private void assertNotBuilt() {
- if (built) {
- throw log.builderAlreadyBuilt();
- }
- }
-
- }
-
- private static GSSCredential wrapCredential(final GSSCredential credential) {
- return new GSSCredential() {
-
- @Override
- public int getUsage(Oid mech) throws GSSException {
- return credential.getUsage(mech);
- }
-
- @Override
- public int getUsage() throws GSSException {
- return credential.getUsage();
- }
-
- @Override
- public int getRemainingLifetime() throws GSSException {
- return credential.getRemainingLifetime();
- }
-
- @Override
- public int getRemainingInitLifetime(Oid mech) throws GSSException {
- return credential.getRemainingInitLifetime(mech);
- }
-
- @Override
- public int getRemainingAcceptLifetime(Oid mech) throws GSSException {
- return credential.getRemainingAcceptLifetime(mech);
- }
-
- @Override
- public GSSName getName(Oid mech) throws GSSException {
- return credential.getName(mech);
- }
-
- @Override
- public GSSName getName() throws GSSException {
- return credential.getName();
- }
-
- @Override
- public Oid[] getMechs() throws GSSException {
- return credential.getMechs();
- }
-
- @Override
- public void dispose() throws GSSException {
- // Prevent disposal of our credential.
- }
-
- @Override
- public void add(GSSName name, int initLifetime, int acceptLifetime, Oid mech, int usage) throws GSSException {
- credential.add(name, initLifetime, acceptLifetime, mech, usage);
- }
-
- };
- }
-}
diff --git a/base/pom.xml b/base/pom.xml
index 5ff7e107802..1c30ef98cd9 100644
--- a/base/pom.xml
+++ b/base/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
4.0.0
diff --git a/credential/base/pom.xml b/credential/base/pom.xml
index c85711ed292..2c98ff8943c 100644
--- a/credential/base/pom.xml
+++ b/credential/base/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/credential/base/src/main/java/org/wildfly/security/key/KeyUtil.java b/credential/base/src/main/java/org/wildfly/security/key/KeyUtil.java
index 234734c96bb..4ab3edd0236 100644
--- a/credential/base/src/main/java/org/wildfly/security/key/KeyUtil.java
+++ b/credential/base/src/main/java/org/wildfly/security/key/KeyUtil.java
@@ -37,6 +37,7 @@
import java.security.interfaces.RSAMultiPrimePrivateCrtKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.DSAParameterSpec;
import java.security.spec.ECParameterSpec;
import java.util.Arrays;
import java.util.Objects;
@@ -99,7 +100,9 @@ public static P getParameters(Key key, Class<
} else if (key instanceof RSAKey && paramSpecClass.isAssignableFrom(RSAParameterSpec.class)) {
return paramSpecClass.cast(new RSAParameterSpec((RSAKey) key));
} else if (key instanceof DSAKey && paramSpecClass.isAssignableFrom(DSAParams.class)) {
- return paramSpecClass.cast(((DSAKey) key).getParams());
+ final DSAKey dsaKey = (DSAKey) key;
+ final DSAParams dsaParams = dsaKey.getParams();
+ return paramSpecClass.cast(new DSAParameterSpec(dsaParams.getP(), dsaParams.getQ(), dsaParams.getG()));
} else if (key instanceof ECKey && paramSpecClass.isAssignableFrom(ECParameterSpec.class)) {
return paramSpecClass.cast(((ECKey) key).getParams());
} else if (key instanceof DHKey && paramSpecClass.isAssignableFrom(DHParameterSpec.class)) {
diff --git a/credential/base/src/main/java/org/wildfly/security/keystore/PasswordKeyStoreSpi.java b/credential/base/src/main/java/org/wildfly/security/keystore/PasswordKeyStoreSpi.java
index b5c592f6303..3cf4959452a 100644
--- a/credential/base/src/main/java/org/wildfly/security/keystore/PasswordKeyStoreSpi.java
+++ b/credential/base/src/main/java/org/wildfly/security/keystore/PasswordKeyStoreSpi.java
@@ -91,7 +91,9 @@ public void engineSetEntry(final String alias, final KeyStore.Entry entry, final
if (protParam != null) {
throw log.keyCannotBeProtected(alias);
}
- HashMap map, newMap;
+ HashMap map;
+ HashMap newMap;
+
do {
map = pwRef.get();
if (map == null) {
diff --git a/credential/source/deprecated/pom.xml b/credential/source/deprecated/pom.xml
index 2f625828242..2c91de751de 100644
--- a/credential/source/deprecated/pom.xml
+++ b/credential/source/deprecated/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../../pom.xml
diff --git a/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/CallbackHandlerCredentialSource.java b/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/CallbackHandlerCredentialSource.java
index 509491c2252..deb676a58e8 100644
--- a/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/CallbackHandlerCredentialSource.java
+++ b/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/CallbackHandlerCredentialSource.java
@@ -37,7 +37,7 @@
* A credential source which is backed by a callback handler.
*
* @author David M. Lloyd
- * @deprecated Use {@link org.wildfly.security.credential.source.impl.CallbackHandlerCredentialSource} instead
+ * @deprecated Use {@link org.wildfly.security.credential.source.impl.CallbackHandlerCredentialSource org.wildfly.security.credential.source.impl.CallbackHandlerCredentialSource} instead
*/
@Deprecated
public final class CallbackHandlerCredentialSource implements CredentialSource {
diff --git a/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/CommandCredentialSource.java b/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/CommandCredentialSource.java
index b5914bda876..2265e3da231 100644
--- a/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/CommandCredentialSource.java
+++ b/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/CommandCredentialSource.java
@@ -52,7 +52,7 @@
* A credential source which acquires a credential from the command line.
*
* @author David M. Lloyd
- * @deprecated Use {@link org.wildfly.security.credential.source.impl.CommandCredentialSource} instead
+ * @deprecated Use {@link org.wildfly.security.credential.source.impl.CommandCredentialSource org.wildfly.security.credential.source.impl.CommandCredentialSource} instead
*/
@Deprecated
public final class CommandCredentialSource implements CredentialSource {
diff --git a/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/CredentialStoreCredentialSource.java b/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/CredentialStoreCredentialSource.java
index 76e35fd81f9..234b0d32d66 100644
--- a/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/CredentialStoreCredentialSource.java
+++ b/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/CredentialStoreCredentialSource.java
@@ -37,7 +37,7 @@
*
* @author David M. Lloyd
* @author Peter Skopek
- * @deprecated Use {@link org.wildfly.security.credential.source.impl.CredentialStoreCredentialSource} instead
+ * @deprecated Use {@link org.wildfly.security.credential.source.impl.CredentialStoreCredentialSource org.wildfly.security.credential.source.impl.CredentialStoreCredentialSource} instead
*/
@Deprecated
public final class CredentialStoreCredentialSource implements CredentialSource {
diff --git a/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/FactoryCredentialSource.java b/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/FactoryCredentialSource.java
index 3e5944f3673..088ff37a36e 100644
--- a/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/FactoryCredentialSource.java
+++ b/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/FactoryCredentialSource.java
@@ -31,7 +31,7 @@
* A credential source which is backed by a credential security factory.
*
* @author Martin Mazanek
- * @deprecated Use {@link org.wildfly.security.credential.source.impl.FactoryCredentialSource} instead
+ * @deprecated Use {@link org.wildfly.security.credential.source.impl.FactoryCredentialSource org.wildfly.security.credential.source.impl.FactoryCredentialSource} instead
*/
@Deprecated
public class FactoryCredentialSource implements CredentialSource {
diff --git a/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/KeyStoreCredentialSource.java b/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/KeyStoreCredentialSource.java
index 5b12588b703..40087d618b4 100644
--- a/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/KeyStoreCredentialSource.java
+++ b/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/KeyStoreCredentialSource.java
@@ -43,7 +43,7 @@
* A credential source which is backed by a key store entry.
*
* @author David M. Lloyd
- * @deprecated Use {@link org.wildfly.security.credential.source.impl.KeyStoreCredentialSource} instead
+ * @deprecated Use {@link org.wildfly.security.credential.source.impl.KeyStoreCredentialSource org.wildfly.security.credential.source.impl.KeyStoreCredentialSource} instead
*/
@Deprecated
public final class KeyStoreCredentialSource implements CredentialSource {
diff --git a/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/LocalKerberosCredentialSource.java b/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/LocalKerberosCredentialSource.java
index 11e521dc5ed..c27d20012b0 100644
--- a/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/LocalKerberosCredentialSource.java
+++ b/credential/source/deprecated/src/main/java/org/wildfly/security/credential/source/LocalKerberosCredentialSource.java
@@ -40,7 +40,7 @@
*
* Successful obtaining from cache requires set system property {@code javax.security.auth.useSubjectCredsOnly} to {@code false}.
*
- * @deprecated Kerberos based authentication mechanism obtains credential himself, see {@link org.wildfly.security.credential.source.impl.LocalKerberosCredentialSource} to use with the new wildfly-elytron-credential-source-impl module
+ * @deprecated Kerberos based authentication mechanism obtains credential himself, see {@link org.wildfly.security.credential.source.impl.LocalKerberosCredentialSource org.wildfly.security.credential.source.impl.LocalKerberosCredentialSource} to use with the new wildfly-elytron-credential-source-impl module
*
* @author Jan Kalina
*/
diff --git a/credential/source/impl/pom.xml b/credential/source/impl/pom.xml
index d87ea7011e5..64e2fb398a1 100644
--- a/credential/source/impl/pom.xml
+++ b/credential/source/impl/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../../pom.xml
diff --git a/credential/store/pom.xml b/credential/store/pom.xml
index b0dda23a278..ac6bc803d5c 100644
--- a/credential/store/pom.xml
+++ b/credential/store/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/credential/store/src/main/java/org/wildfly/security/credential/store/impl/VaultObjectInputStream.java b/credential/store/src/main/java/org/wildfly/security/credential/store/impl/VaultObjectInputStream.java
index c71e9202ee2..566a144e16c 100644
--- a/credential/store/src/main/java/org/wildfly/security/credential/store/impl/VaultObjectInputStream.java
+++ b/credential/store/src/main/java/org/wildfly/security/credential/store/impl/VaultObjectInputStream.java
@@ -31,9 +31,10 @@ final class VaultObjectInputStream extends ObjectInputStream {
protected Class> resolveClass(final ObjectStreamClass desc) throws IOException, ClassNotFoundException {
final String name = desc.getName();
- switch (name) {
- case SecurityVaultData.PICKETBOX_CLASS_NAME: return SecurityVaultData.class;
- default: return super.resolveClass(desc);
+ if (name.equals(SecurityVaultData.PICKETBOX_CLASS_NAME)) {
+ return SecurityVaultData.class;
+ } else {
+ return super.resolveClass(desc);
}
}
}
diff --git a/digest/pom.xml b/digest/pom.xml
index 506d3b75783..fd791d6cfde 100644
--- a/digest/pom.xml
+++ b/digest/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
4.0.0
diff --git a/dynamic-ssl/pom.xml b/dynamic-ssl/pom.xml
new file mode 100644
index 00000000000..0149158d921
--- /dev/null
+++ b/dynamic-ssl/pom.xml
@@ -0,0 +1,82 @@
+
+
+
+ org.wildfly.security
+ wildfly-elytron-parent
+ 2.6.1.CR1-SNAPSHOT
+
+
+ 4.0.0
+
+ wildfly-elytron-dynamic-ssl
+
+ WildFly Elytron - Dynamic SSL
+ WildFly Security Dynamic SSL Implementation
+
+
+ org.jboss.logging
+ jboss-logging-annotations
+ provided
+
+
+ org.jboss.logging
+ jboss-logging
+ provided
+
+
+ org.jboss.logging
+ jboss-logging-processor
+ provided
+
+
+ org.jboss.logmanager
+ jboss-logmanager
+ provided
+
+
+ org.wildfly.security
+ wildfly-elytron-client
+
+
+ org.wildfly.security
+ wildfly-elytron-tests-common
+ test
+ test-jar
+
+
+ org.kohsuke.metainf-services
+ metainf-services
+ provided
+
+
+ org.wildfly.common
+ wildfly-common
+ compile
+
+
+
+
+ junit
+ junit
+ test
+
+
+
+ com.squareup.okhttp3
+ mockwebserver
+ test
+
+
+ org.mock-server
+ mockserver-netty
+ test
+
+
+ org.wildfly.client
+ wildfly-client-config
+ test
+
+
+
diff --git a/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContext.java b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContext.java
new file mode 100644
index 00000000000..500d03badc2
--- /dev/null
+++ b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContext.java
@@ -0,0 +1,56 @@
+/*
+ * 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.dynamic.ssl;
+
+import org.wildfly.security.auth.client.ActiveSessionsSSLContext;
+
+import javax.net.ssl.SSLContext;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * SSLContext that resolves which SSLContext to use based on peer's host and port information.
+ *
+ * @author Diana Krepinska
+ */
+public final class DynamicSSLContext extends SSLContext implements ActiveSessionsSSLContext {
+
+ private static SSLContext resolverSSLContext(DynamicSSLContextSPI dynamicSSLContextSPIImpl) throws NoSuchAlgorithmException, DynamicSSLContextException {
+ return dynamicSSLContextSPIImpl.getConfiguredDefault() == null ?
+ SSLContext.getDefault() : dynamicSSLContextSPIImpl.getConfiguredDefault();
+ }
+
+ /**
+ * This constructor uses ServiceLoader to find provider of DynamicSSLContextSPI on classpath.
+ */
+ public DynamicSSLContext() throws NoSuchAlgorithmException {
+ // this does not use provider and protocol from DynamicSSLContextSPI implementation found on classpath
+ // to avoid this ServiceLoader.load would have to be called 3 times in separate static method
+ super(new DynamicSSLContextSpiImpl(), SSLContext.getDefault().getProvider(), SSLContext.getDefault().getProtocol());
+ }
+
+ /**
+ * This constructor uses received DynamicSSLContextSPI implementation or finds it on classpath if received is null.
+ *
+ * @param dynamicSSLContextSPIImpl DynamicSSLContextSPI implementation to use. If null then ServiceLoader is used to locate it on classpath.
+ */
+ public DynamicSSLContext(DynamicSSLContextSPI dynamicSSLContextSPIImpl) throws NoSuchAlgorithmException, DynamicSSLContextException {
+ super(new DynamicSSLContextSpiImpl(dynamicSSLContextSPIImpl),
+ resolverSSLContext(dynamicSSLContextSPIImpl).getProvider(),
+ resolverSSLContext(dynamicSSLContextSPIImpl).getProtocol());
+ }
+}
diff --git a/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextException.java b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextException.java
new file mode 100644
index 00000000000..a47dbea76b0
--- /dev/null
+++ b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextException.java
@@ -0,0 +1,42 @@
+/*
+ * 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.dynamic.ssl;
+
+/**
+ * Exception to indicate a failure related to the DynamicSSLContext.
+ *
+ * @author Diana Krepinska
+ */
+public class DynamicSSLContextException extends Exception {
+ private static final long serialVersionUID = 894798122053539237L;
+
+ public DynamicSSLContextException() {
+ }
+
+ public DynamicSSLContextException(String msg) {
+ super(msg);
+ }
+
+ public DynamicSSLContextException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public DynamicSSLContextException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextImpl.java b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextImpl.java
new file mode 100644
index 00000000000..153f59c5a0d
--- /dev/null
+++ b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextImpl.java
@@ -0,0 +1,91 @@
+/*
+ * 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.dynamic.ssl;
+
+import org.kohsuke.MetaInfServices;
+import org.wildfly.security.auth.client.AuthenticationContext;
+import org.wildfly.security.auth.client.AuthenticationContextConfigurationClient;
+
+import javax.net.ssl.SSLContext;
+import java.net.URI;
+import java.security.AccessController;
+import java.security.GeneralSecurityException;
+import java.security.PrivilegedAction;
+import java.util.List;
+
+import static org.wildfly.common.Assert.checkNotNullParam;
+
+/**
+ * Elytron client implementation of DynamicSSLContextSPI. It uses configuration from either provided instance of AuthenticationContext
+ * or from current AuthenticationContext if a configuration was not provided.
+ *
+ * @author Diana Krepinska (Vilkolakova)
+ */
+@MetaInfServices(value = DynamicSSLContextSPI.class)
+public class DynamicSSLContextImpl implements DynamicSSLContextSPI {
+
+ private final AuthenticationContextConfigurationClient AUTH_CONTEXT_CLIENT =
+ AccessController.doPrivileged((PrivilegedAction) AuthenticationContextConfigurationClient::new);
+ private AuthenticationContext authenticationContext;
+ private SSLContext configuredDefaultSSLContext;
+ private List configuredSSLContexts;
+
+ public DynamicSSLContextImpl() throws GeneralSecurityException {
+ }
+
+ public DynamicSSLContextImpl(AuthenticationContext authenticationContext) throws GeneralSecurityException {
+ checkNotNullParam("authenticationContext", authenticationContext);
+ this.authenticationContext = authenticationContext;
+ this.configuredSSLContexts = AUTH_CONTEXT_CLIENT.getConfiguredSSLContexts(authenticationContext);
+ this.configuredDefaultSSLContext = AUTH_CONTEXT_CLIENT.getDefaultSSLContext(authenticationContext);
+ }
+
+ @Override
+ public SSLContext getConfiguredDefault() throws DynamicSSLContextException {
+ if (this.configuredDefaultSSLContext != null) {
+ return this.configuredDefaultSSLContext;
+ }
+ try {
+ return AUTH_CONTEXT_CLIENT.getDefaultSSLContext(AuthenticationContext.captureCurrent());
+ } catch (GeneralSecurityException e) {
+ throw ElytronMessages.log.cannotObtainDefaultSSLContext(e);
+ }
+ }
+
+ @Override
+ public List getConfiguredSSLContexts() throws DynamicSSLContextException {
+ if (this.configuredSSLContexts != null) {
+ return this.configuredSSLContexts;
+ }
+ try {
+ return AUTH_CONTEXT_CLIENT.getConfiguredSSLContexts(AuthenticationContext.captureCurrent());
+ } catch (GeneralSecurityException e) {
+ throw ElytronMessages.log.cannotObtainConfiguredSSLContexts(e);
+ }
+ }
+
+ @Override
+ public SSLContext getSSLContext(URI uri) throws DynamicSSLContextException {
+ try {
+ return AUTH_CONTEXT_CLIENT.getSSLContext(uri, authenticationContext == null ? AuthenticationContext.captureCurrent() : authenticationContext);
+ } catch (GeneralSecurityException e) {
+ throw ElytronMessages.log.cannotObtainSSLContextForGivenURI(e);
+ }
+ }
+}
diff --git a/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextSPI.java b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextSPI.java
new file mode 100644
index 00000000000..91985a60fe3
--- /dev/null
+++ b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextSPI.java
@@ -0,0 +1,52 @@
+/*
+ * 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.dynamic.ssl;
+
+import javax.net.ssl.SSLContext;
+import java.net.URI;
+import java.util.List;
+
+/**
+ * This interface provides configuration that is used by DynamicSSLContext.
+ *
+ * @author Diana Krepinska
+ */
+public interface DynamicSSLContextSPI {
+
+ /**
+ * Get SSLContext that will be used as a default, eg. when no URI is provided.
+ *
+ * @return configured default SSLContext
+ */
+ SSLContext getConfiguredDefault() throws DynamicSSLContextException;
+
+ /**
+ * Get list of all configured SSLContexts. This is used to obtain cipher suites supported by all SSLContexts.
+ *
+ * @return list of all configured SSLContexts
+ */
+ List getConfiguredSSLContexts() throws DynamicSSLContextException;
+
+ /**
+ * Get the SSLContext that matches the given URI.
+ *
+ * @return SSLContext that matches the given URI
+ */
+ SSLContext getSSLContext(URI uri) throws DynamicSSLContextException;
+}
diff --git a/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextSpiImpl.java b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextSpiImpl.java
new file mode 100644
index 00000000000..d3085b96366
--- /dev/null
+++ b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextSpiImpl.java
@@ -0,0 +1,145 @@
+/*
+ * 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.dynamic.ssl;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLContextSpi;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.SSLSessionContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Iterator;
+import java.util.ServiceLoader;
+
+/**
+ * SSLContextSpi that uses ServiceLoader to find implementations of DynamicSSLContextSPI.
+ * DynamicSSLContextSPI implementation is being used to obtain authentication configuration for DynamicSSLContext.
+ * if no provider is found then SSLContext.getDefault() is used.
+ *
+ * @author Diana Krepinska
+ */
+final class DynamicSSLContextSpiImpl extends SSLContextSpi {
+
+ private final DynamicSSLContextSPI dynamicSSLContextImpl;
+ private volatile SSLSocketFactory sslSocketFactory;
+
+ DynamicSSLContextSpiImpl() {
+ this(null);
+ }
+
+ DynamicSSLContextSpiImpl(DynamicSSLContextSPI dynamicSSLContextSPIImpl) {
+ if (dynamicSSLContextSPIImpl != null) {
+ dynamicSSLContextImpl = dynamicSSLContextSPIImpl;
+ } else {
+ Iterator dynamicSSLContextSPIIterator = ServiceLoader.load(DynamicSSLContextSPI.class).iterator();
+ if (dynamicSSLContextSPIIterator.hasNext()) {
+ dynamicSSLContextImpl = dynamicSSLContextSPIIterator.next();
+ } else {
+ dynamicSSLContextImpl = null;
+ }
+ }
+ }
+
+ private SSLContext getConfiguredDefaultSSLContext() {
+ try {
+ if (dynamicSSLContextImpl != null) {
+ SSLContext configuredDefault = dynamicSSLContextImpl.getConfiguredDefault();
+ if (configuredDefault != null) {
+ return configuredDefault;
+ }
+ }
+ return SSLContext.getDefault();
+ } catch (NoSuchAlgorithmException | DynamicSSLContextException e) {
+ throw ElytronMessages.log.cannotObtainConfiguredDefaultSSLContext();
+ }
+ }
+
+ @Override
+ protected void engineInit(KeyManager[] keyManagers, TrustManager[] trustManagers, SecureRandom secureRandom) {
+ // initialization of SSL context is delegated to providers of {@link org.wildfly.security.dynamic.ssl.DynamicSSLContextSPI}
+ }
+
+ @Override
+ protected SSLSocketFactory engineGetSocketFactory() {
+ if (dynamicSSLContextImpl == null) {
+ return this.getConfiguredDefaultSSLContext().getSocketFactory();
+ }
+ if (sslSocketFactory == null) {
+ synchronized (this) {
+ if (sslSocketFactory == null) {
+ sslSocketFactory = new DynamicSSLSocketFactory(this.getConfiguredDefaultSSLContext().getSocketFactory(), dynamicSSLContextImpl);
+ }
+ }
+ }
+ return sslSocketFactory;
+ }
+
+ @Override
+ protected SSLServerSocketFactory engineGetServerSocketFactory() {
+ return this.getConfiguredDefaultSSLContext().getServerSocketFactory();
+ }
+
+ @Override
+ protected SSLEngine engineCreateSSLEngine() {
+ return this.getConfiguredDefaultSSLContext().createSSLEngine();
+ }
+
+ @Override
+ protected SSLEngine engineCreateSSLEngine(String host, int port) throws IllegalStateException {
+ try {
+ if (dynamicSSLContextImpl == null) {
+ return this.getConfiguredDefaultSSLContext().createSSLEngine(host, port);
+ }
+ SSLContext sslContext = dynamicSSLContextImpl
+ .getSSLContext(new URI(null, null, host, port, null, null, null));
+ if (sslContext == null) {
+ throw ElytronMessages.log.receivedSSLContextFromDynamicSSLContextProviderWasNull();
+ }
+ if (sslContext instanceof DynamicSSLContext && sslContext.getSocketFactory().equals(this.engineGetSocketFactory())) {
+ throw ElytronMessages.log.dynamicSSLContextCreatesLoop();
+ }
+ return sslContext.createSSLEngine(host, port);
+ } catch (URISyntaxException e) {
+ throw ElytronMessages.log.couldNotCreateURI();
+ } catch (DynamicSSLContextException e) {
+ throw ElytronMessages.log.couldNotCreateDynamicSSLContextEngine();
+ }
+ }
+
+ @Override
+ protected SSLSessionContext engineGetServerSessionContext() {
+ throw new UnsupportedOperationException(ElytronMessages.log.dynamicSSLContextDoesNotSupportSessions());
+ }
+
+ @Override
+ protected SSLSessionContext engineGetClientSessionContext() {
+ throw new UnsupportedOperationException(ElytronMessages.log.dynamicSSLContextDoesNotSupportSessions());
+ }
+
+ @Override
+ protected SSLParameters engineGetSupportedSSLParameters() {
+ return this.getConfiguredDefaultSSLContext().getSupportedSSLParameters();
+ }
+}
diff --git a/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLSocketFactory.java b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLSocketFactory.java
new file mode 100644
index 00000000000..a06badab0ce
--- /dev/null
+++ b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLSocketFactory.java
@@ -0,0 +1,160 @@
+/*
+ * 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.dynamic.ssl;
+
+import org.wildfly.common.Assert;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * SSLSocketFactory that is being used by DynamicSSLContext.
+ *
+ * @author Diana Krepinska
+ */
+final class DynamicSSLSocketFactory extends SSLSocketFactory {
+
+ private DynamicSSLContextSPI dynamicSSLContextImpl;
+ private volatile String[] intersectionCipherSuite;
+ private SSLSocketFactory configuredDefaultSslSocketFactory;
+
+ DynamicSSLSocketFactory(SSLSocketFactory configuredDefaultSslSocketFactory, DynamicSSLContextSPI dynamicSSLContextImpl) {
+ super();
+ Assert.assertNotNull(configuredDefaultSslSocketFactory);
+ Assert.assertNotNull(dynamicSSLContextImpl);
+ this.configuredDefaultSslSocketFactory = configuredDefaultSslSocketFactory;
+ this.dynamicSSLContextImpl = dynamicSSLContextImpl;
+ }
+
+ @Override
+ public Socket createSocket() throws IOException {
+ return configuredDefaultSslSocketFactory.createSocket();
+ }
+
+ @Override
+ public Socket createSocket(InetAddress address, int port) throws IOException {
+ return createSocketBasedOnPeerInfo(null, port, address, null, null, null, null);
+ }
+
+ @Override
+ public Socket createSocket(String host, int port) throws IOException {
+ return createSocketBasedOnPeerInfo(host, port, null, null, null, null, null);
+ }
+
+ @Override
+ public Socket createSocket(String host, int port, InetAddress localAddress, int localPort) throws IOException {
+ return createSocketBasedOnPeerInfo(host, port, null, localAddress, localPort, null, null);
+ }
+
+ @Override
+ public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
+ return createSocketBasedOnPeerInfo(null, port, address, localAddress, localPort, null, null);
+ }
+
+ @Override
+ public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
+ return createSocketBasedOnPeerInfo(host, port, null, null, null, socket, autoClose);
+ }
+
+ @Override
+ public String[] getDefaultCipherSuites() {
+ return configuredDefaultSslSocketFactory.getDefaultCipherSuites();
+ }
+
+ @Override
+ public String[] getSupportedCipherSuites() {
+ String[] val = intersectionCipherSuite;
+ if (val == null) {
+ synchronized (this) {
+ val = intersectionCipherSuite;
+ if (intersectionCipherSuite == null) {
+ val = intersectionCipherSuite = getIntersection();
+ }
+ }
+ }
+ return val;
+ }
+
+ private Socket createSocketBasedOnPeerInfo(String hostname, Integer port, InetAddress address, InetAddress localAddress, Integer localPort, Socket socket, Boolean autoClose) throws IOException {
+ try {
+ SSLContext sslContext = this.dynamicSSLContextImpl.getSSLContext(new URI(null, null, hostname == null ? address.getHostName() : hostname, port, null, null, null));
+ if (sslContext == null) {
+ throw ElytronMessages.log.configuredSSLContextIsNull();
+ }
+ SSLSocketFactory socketFactory = sslContext.getSocketFactory();
+ if (socketFactory instanceof DynamicSSLSocketFactory && socketFactory.equals(this)) {
+ throw ElytronMessages.log.dynamicSSLContextCreatesLoop();
+ }
+ // resolve socket
+ if (socket != null && autoClose != null) {
+ return socketFactory.createSocket(socket, hostname, port, autoClose);
+ }
+
+ // resolves InetAddresses callbacks
+ if (address != null) {
+ return localAddress == null ?
+ socketFactory.createSocket(address, port) : socketFactory.createSocket(address, port, localAddress, localPort);
+ }
+ if (localAddress != null && localPort != null) {
+ return socketFactory.createSocket(hostname, port, localAddress, localPort);
+ }
+ return socketFactory.createSocket(hostname, port);
+ } catch (URISyntaxException e) {
+ throw new UnknownHostException(e.getMessage());
+ } catch (DynamicSSLContextException e) {
+ throw new IOException(e);
+ }
+ }
+
+ private String[] getIntersection() {
+ List sslContexts;
+ try {
+ sslContexts = dynamicSSLContextImpl.getConfiguredSSLContexts();
+ } catch (DynamicSSLContextException e) {
+ throw ElytronMessages.log.unableToGetConfiguredSSLContexts();
+ }
+ if (sslContexts == null) {
+ throw ElytronMessages.log.configuredSSLContextsAreNull();
+ }
+ Map counts = new HashMap<>();
+ List intersection = new ArrayList<>();
+ sslContexts.forEach(c -> {
+ String[] cipherSuites = c.getSocketFactory().getSupportedCipherSuites();
+ for (String cipherSuite : cipherSuites) {
+ counts.merge(cipherSuite, 1, (a, b) -> a + b);
+ }
+ });
+ List finalSslContexts = sslContexts;
+ counts.forEach((c, v) -> {
+ if (finalSslContexts.size() == v) {
+ intersection.add(c);
+ }
+ });
+ return intersection.toArray(new String[0]);
+ }
+}
diff --git a/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/ElytronMessages.java b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/ElytronMessages.java
new file mode 100644
index 00000000000..feab5f75ee2
--- /dev/null
+++ b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/ElytronMessages.java
@@ -0,0 +1,77 @@
+/*
+ * 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.dynamic.ssl;
+
+import org.jboss.logging.BasicLogger;
+import org.jboss.logging.Logger;
+import org.jboss.logging.annotations.Cause;
+import org.jboss.logging.annotations.Message;
+import org.jboss.logging.annotations.MessageLogger;
+import org.jboss.logging.annotations.ValidIdRange;
+import org.jboss.logging.annotations.ValidIdRanges;
+
+/**
+ * Log messages and exceptions for Elytron.
+ *
+ * @author David M. Lloyd
+ * @author Darran Lofthouse
+ */
+@MessageLogger(projectCode = "ELY", length = 5)
+@ValidIdRanges({
+ @ValidIdRange(min = 21000, max = 21999)
+})
+interface ElytronMessages extends BasicLogger {
+
+ ElytronMessages log = Logger.getMessageLogger(ElytronMessages.class, "org.wildfly.security");
+
+ @Message(id = 21000, value = "DynamicSSLContext creates loop")
+ IllegalStateException dynamicSSLContextCreatesLoop();
+
+ @Message(id = 21001, value = "Received SSLContext from DynamicSSLContextProvider was null")
+ IllegalStateException receivedSSLContextFromDynamicSSLContextProviderWasNull();
+
+ @Message(id = 21002, value = "Dynamic SSLContext does not support sessions")
+ UnsupportedOperationException dynamicSSLContextDoesNotSupportSessions();
+
+ @Message(id = 21003, value = "Provider for DynamicSSLContextSPI threw an exception when getting configured SSLContexts")
+ IllegalStateException unableToGetConfiguredSSLContexts();
+
+ @Message(id = 21004, value = "Provider for DynamicSSLContextSPI returned null configured SSLContexts")
+ IllegalStateException configuredSSLContextsAreNull();
+
+ @Message(id = 21005, value = "Cannot obtain default SSLContext from DynamicSSLContext implementation")
+ IllegalStateException cannotObtainConfiguredDefaultSSLContext();
+
+ @Message(id = 21006, value = "Could not create URI from host and port")
+ IllegalStateException couldNotCreateURI();
+
+ @Message(id = 21007, value = "Could not create dynamic ssl context engine")
+ IllegalStateException couldNotCreateDynamicSSLContextEngine();
+
+ @Message(id = 21008, value = "Provider for DynamicSSLContextSPI returned null SSLContext")
+ IllegalStateException configuredSSLContextIsNull();
+
+ @Message(id = 21009, value = "Obtaining of the default SSLContext from current authentication context resulted in exception.")
+ DynamicSSLContextException cannotObtainDefaultSSLContext(@Cause Throwable cause);
+
+ @Message(id = 21010, value = "Obtaining of all configured SSLContexts from current authentication context resulted in exception.")
+ DynamicSSLContextException cannotObtainConfiguredSSLContexts(@Cause Throwable cause);
+
+ @Message(id = 21011, value = "Obtaining of the SSLContext from current authentication context and provided URI resulted in exception.")
+ DynamicSSLContextException cannotObtainSSLContextForGivenURI(@Cause Throwable cause);
+}
diff --git a/dynamic-ssl/src/test/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextTest.java b/dynamic-ssl/src/test/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextTest.java
new file mode 100644
index 00000000000..6c4378f34f6
--- /dev/null
+++ b/dynamic-ssl/src/test/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextTest.java
@@ -0,0 +1,454 @@
+/*
+ * 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.dynamic.ssl;
+
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.wildfly.security.auth.client.AuthenticationContext;
+import org.wildfly.security.auth.client.AuthenticationContextConfigurationClient;
+import org.wildfly.security.auth.client.ElytronXmlParser;
+import org.wildfly.security.auth.client.InvalidAuthenticationConfigurationException;
+import org.wildfly.security.auth.client.MatchRule;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.GeneralSecurityException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivilegedAction;
+import java.security.cert.CertificateException;
+
+import static java.security.AccessController.doPrivileged;
+import static org.wildfly.security.dynamic.ssl.SSLServerSocketTestInstance.ServerThread.STATUS_OK;
+
+/**
+ * Functional tests of DynamicSSLContext.
+ *
+ * @author Diana Krepinska (Vilkolakova)
+ */
+public class DynamicSSLContextTest {
+ static final String RESOURCES = "./target/keystores/";
+ private static org.wildfly.security.dynamic.ssl.SSLServerSocketTestInstance sslServerSocketTestInstancePort10001;
+ private static org.wildfly.security.dynamic.ssl.SSLServerSocketTestInstance sslServerSocketTestInstancePort10002;
+ private static org.wildfly.security.dynamic.ssl.SSLServerSocketTestInstance sslServerSocketTestInstancePort10003;
+ private static org.wildfly.security.dynamic.ssl.SSLServerSocketTestInstance sslServerSocketTestInstancePort10000Default;
+
+ @BeforeClass
+ public static void before() throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException {
+ DynamicSSLTestUtils.createKeystores();
+ sslServerSocketTestInstancePort10001 = new SSLServerSocketTestInstance(RESOURCES + "server1.keystore.jks", RESOURCES + "server1.truststore.jks", 10001);
+ sslServerSocketTestInstancePort10002 = new SSLServerSocketTestInstance(RESOURCES + "server2.keystore.jks", RESOURCES + "server2.truststore.jks", 10002);
+ sslServerSocketTestInstancePort10003 = new SSLServerSocketTestInstance(RESOURCES + "server3.keystore.jks", RESOURCES + "server3.truststore.jks", 10003);
+ sslServerSocketTestInstancePort10000Default = new SSLServerSocketTestInstance(RESOURCES + "default-server.keystore.jks", RESOURCES + "default-server.truststore.jks", 10000);
+
+ sslServerSocketTestInstancePort10001.run();
+ sslServerSocketTestInstancePort10002.run();
+ sslServerSocketTestInstancePort10003.run();
+ sslServerSocketTestInstancePort10000Default.run();
+ }
+
+ @AfterClass
+ public static void after() {
+ sslServerSocketTestInstancePort10001.stop();
+ sslServerSocketTestInstancePort10002.stop();
+ sslServerSocketTestInstancePort10003.stop();
+ sslServerSocketTestInstancePort10000Default.stop();
+ org.wildfly.security.dynamic.ssl.DynamicSSLTestUtils.deleteKeystores();
+ }
+
+ @Test
+ public void smokeTestWith4Servers() throws NoSuchAlgorithmException {
+ SSLContext dynamicSSLContext = new DynamicSSLContext();
+ SSLSocketFactory dynamicSSLContextSocketFactory = dynamicSSLContext.getSocketFactory();
+ getAuthenticationContext("wildfly-config-dynamic-ssl-test.xml").run(() -> {
+ try {
+ SSLSocket clientSslSocket1 = (SSLSocket) dynamicSSLContextSocketFactory.createSocket("localhost", 10001);
+ clientSslSocket1.setUseClientMode(true);
+ clientSslSocket1.setReuseAddress(true);
+ checkOutputIsOK(clientSslSocket1);
+ clientSslSocket1.close();
+
+ SSLSocket clientSslSocket2 = (SSLSocket) dynamicSSLContextSocketFactory.createSocket("localhost", 10002);
+ clientSslSocket2.setReuseAddress(true);
+ checkOutputIsOK(clientSslSocket2);
+ clientSslSocket2.close();
+
+ SSLSocket clientSslSocket3 = (SSLSocket) dynamicSSLContextSocketFactory.createSocket("localhost", 10003);
+ clientSslSocket3.setReuseAddress(true);
+ checkOutputIsOK(clientSslSocket3);
+ clientSslSocket3.close();
+
+ SSLSocket clientSslSocket4 = (SSLSocket) dynamicSSLContextSocketFactory.createSocket("localhost", 10000);
+ clientSslSocket4.setReuseAddress(true);
+ checkOutputIsOK(clientSslSocket4);
+ clientSslSocket4.close();
+ } catch (Exception e) {
+ Assert.assertEquals("fine", e.getMessage());
+ }
+ });
+ }
+
+ @Test
+ public void smokeTestAuthenticationContextPassedExplicitly() throws DynamicSSLContextException, GeneralSecurityException {
+ SSLContext dynamicSSLContext = new DynamicSSLContext(new DynamicSSLContextImpl(getAuthenticationContext("wildfly-config-dynamic-ssl-test.xml")));
+ SSLSocketFactory dynamicSSLContextSocketFactory = dynamicSSLContext.getSocketFactory();
+ try {
+ SSLSocket clientSslSocket1 = (SSLSocket) dynamicSSLContextSocketFactory.createSocket("localhost", 10001);
+ clientSslSocket1.setUseClientMode(true);
+ clientSslSocket1.setReuseAddress(true);
+ checkOutputIsOK(clientSslSocket1);
+ clientSslSocket1.close();
+
+ SSLSocket clientSslSocket2 = (SSLSocket) dynamicSSLContextSocketFactory.createSocket("localhost", 10002);
+ clientSslSocket2.setReuseAddress(true);
+ checkOutputIsOK(clientSslSocket2);
+ clientSslSocket2.close();
+
+ SSLSocket clientSslSocket3 = (SSLSocket) dynamicSSLContextSocketFactory.createSocket("localhost", 10003);
+ clientSslSocket3.setReuseAddress(true);
+ checkOutputIsOK(clientSslSocket3);
+ clientSslSocket3.close();
+
+ SSLSocket clientSslSocket4 = (SSLSocket) dynamicSSLContextSocketFactory.createSocket("localhost", 10000);
+ clientSslSocket4.setReuseAddress(true);
+ checkOutputIsOK(clientSslSocket4);
+ clientSslSocket4.close();
+ } catch (Exception e) {
+ Assert.assertEquals("fine", e.getMessage());
+ }
+ }
+
+ @Test(expected = SocketException.class)
+ public void smokeTestWithoutElytronClientContextWillFail() throws NoSuchAlgorithmException, IOException {
+ SSLContext dynamicSSLContext = new DynamicSSLContext();
+ SSLSocketFactory dynamicSSLContextSocketFactory = dynamicSSLContext.getSocketFactory();
+ SSLSocket clientSslSocket1 = (SSLSocket) dynamicSSLContextSocketFactory.createSocket("localhost", 10002);
+ clientSslSocket1.setUseClientMode(true);
+ clientSslSocket1.setReuseAddress(true);
+ checkOutputIsOK(clientSslSocket1);
+ clientSslSocket1.close();
+ }
+
+ @Test
+ public void testCreateSocketByInetAddressPort() throws NoSuchAlgorithmException {
+ SSLContext dynamicSSLContext = new DynamicSSLContext();
+ SSLSocketFactory dynamicSSLContextSocketFactory = dynamicSSLContext.getSocketFactory();
+ getAuthenticationContext("wildfly-config-dynamic-ssl-test.xml").run(() -> {
+ try {
+ SSLSocket clientSslSocket = (SSLSocket) dynamicSSLContextSocketFactory.createSocket(InetAddress.getByName("localhost"), 10002);
+ clientSslSocket.setReuseAddress(true);
+ checkOutputIsOK(clientSslSocket);
+ clientSslSocket.close();
+ } catch (Exception e) {
+ Assert.assertEquals("fine", e.getMessage());
+ }
+ });
+ }
+
+ @Test
+ public void testCreateSocketByHostPortLocalAddressLocalPort() throws NoSuchAlgorithmException {
+ SSLContext dynamicSSLContext = new DynamicSSLContext();
+ SSLSocketFactory dynamicSSLContextSocketFactory = dynamicSSLContext.getSocketFactory();
+ getAuthenticationContext("wildfly-config-dynamic-ssl-test.xml").run(() -> {
+ try {
+ SSLSocket clientSslSocket = (SSLSocket) dynamicSSLContextSocketFactory.createSocket("localhost", 10001, InetAddress.getByName("localhost"), 0);
+ clientSslSocket.setReuseAddress(true);
+ checkOutputIsOK(clientSslSocket);
+ clientSslSocket.close();
+ } catch (Exception e) {
+ Assert.assertEquals("fine", e.getMessage());
+ }
+ });
+ }
+
+ @Test
+ public void testCreateSocketByAddressPortLocalAddressLocalPort() throws NoSuchAlgorithmException {
+ SSLContext dynamicSSLContext = new DynamicSSLContext();
+ SSLSocketFactory dynamicSSLContextSocketFactory = dynamicSSLContext.getSocketFactory();
+ getAuthenticationContext("wildfly-config-dynamic-ssl-test.xml").run(() -> {
+ try {
+ SSLSocket clientSslSocket = (SSLSocket) dynamicSSLContextSocketFactory.createSocket(InetAddress.getByName("localhost"), 10001, InetAddress.getByName("localhost"), 12555);
+ clientSslSocket.setReuseAddress(true);
+ checkOutputIsOK(clientSslSocket);
+ clientSslSocket.close();
+ } catch (Exception e) {
+ Assert.assertEquals("fine", e.getMessage());
+ }
+ });
+ }
+
+ @Test
+ public void testCreateSocketBySocketHostPortAutoCloseTrue() throws NoSuchAlgorithmException {
+ SSLContext dynamicSSLContext = new DynamicSSLContext();
+ SSLSocketFactory dynamicSSLContextSocketFactory = dynamicSSLContext.getSocketFactory();
+ getAuthenticationContext("wildfly-config-dynamic-ssl-test.xml").run(() -> {
+ try {
+ Socket plainSocket = new Socket();
+ plainSocket.connect(new InetSocketAddress("localhost", 10001));
+ SSLSocket clientSslSocket = (SSLSocket) dynamicSSLContextSocketFactory.createSocket(plainSocket, "localhost", 10001, true);
+ clientSslSocket.setReuseAddress(true);
+ checkOutputIsOK(clientSslSocket);
+ clientSslSocket.close();
+ plainSocket.close();
+ } catch (Exception e) {
+ Assert.assertEquals("fine", e.getMessage());
+ }
+ });
+ }
+
+ @Test
+ public void testCreateSocketsBySocketHostPortAutoCloseFalse() throws NoSuchAlgorithmException {
+ SSLContext dynamicSSLContext = new DynamicSSLContext();
+ SSLSocketFactory dynamicSSLContextSocketFactory = dynamicSSLContext.getSocketFactory();
+ getAuthenticationContext("wildfly-config-dynamic-ssl-test.xml").run(() -> {
+ try {
+ Socket plainSocket = new Socket();
+ plainSocket.connect(new InetSocketAddress("localhost", 10001));
+ SSLSocket clientSslSocket = (SSLSocket) dynamicSSLContextSocketFactory.createSocket(plainSocket, "localhost", 10001, false);
+ clientSslSocket.setReuseAddress(true);
+ checkOutputIsOK(clientSslSocket);
+ clientSslSocket.close();
+ plainSocket.close();
+ } catch (Exception e) {
+ Assert.assertEquals("fine", e.getMessage());
+ }
+ });
+ }
+
+ @Test
+ public void testCreateSocketbyHostAndPortAndConfiguredSSLParams2() throws NoSuchAlgorithmException {
+ DynamicSSLContext dynamicSSLContext = new DynamicSSLContext();
+ AuthenticationContext context = getAuthenticationContext("wildfly-config-dynamic-ssl-test.xml");
+ context.run(() -> {
+ try {
+ DynamicSSLSocketFactory dynamicSSLContextSocketFactory = (DynamicSSLSocketFactory) dynamicSSLContext.getSocketFactory();
+ dynamicSSLContext.getDefaultSSLParameters().setCipherSuites(new String[]{"TLS_RSA_WITH_AES_128_CBC_SHA256"});
+ SSLSocket clientSslSocket = (SSLSocket) dynamicSSLContextSocketFactory.createSocket();
+ SSLParameters sslParameters = clientSslSocket.getSSLParameters();
+ sslParameters.setCipherSuites(new String[]{"TLS_RSA_WITH_AES_128_CBC_SHA256"});
+ clientSslSocket.setSSLParameters(sslParameters);
+ dynamicSSLContext.getDefaultSSLParameters().setCipherSuites(new String[]{"TLS_RSA_WITH_AES_128_CBC_SHA256"});
+ clientSslSocket.connect(new InetSocketAddress("localhost", 10000));
+ clientSslSocket.startHandshake();
+ Assert.assertEquals("TLS_RSA_WITH_AES_128_CBC_SHA256", clientSslSocket.getSession().getCipherSuite());
+ checkOutputIsOK(clientSslSocket);
+ clientSslSocket.close();
+ } catch (Exception e) {
+ Assert.assertEquals("fine", e.getMessage());
+ }
+ });
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void checkExceptionThrownClientSessionContext() throws Exception {
+ SSLContext sslContext = new DynamicSSLContext();
+ sslContext.getClientSessionContext();
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void checkExceptionThrownServerSessionContext() throws Exception {
+ SSLContext sslContext = new DynamicSSLContext();
+ sslContext.getServerSessionContext();
+ }
+
+ // thorough testing of sslEngine would need a lot of code with socket implementation that is pretty low level
+ // it is reasonable to assume that it is being tested anyway since sockets created by SSLSocketFactory seem to always use this SSLEngine
+ // here I at least test that the SSLEngine was created with correct host and port
+ @Test
+ public void smokeTestCorrectSSLEngineIsUsed() throws NoSuchAlgorithmException {
+ DynamicSSLContext dynamicSSLContext = new DynamicSSLContext();
+ SSLEngine sslEngine = dynamicSSLContext.createSSLEngine("localhost", 10000);
+ Assert.assertEquals("localhost", sslEngine.getPeerHost());
+ Assert.assertEquals(10000, sslEngine.getPeerPort());
+
+ SSLEngine sslEngine2 = dynamicSSLContext.createSSLEngine();
+ Assert.assertNull(sslEngine2.getPeerHost());
+ Assert.assertEquals(-1, sslEngine2.getPeerPort());
+ }
+
+ @Test
+ public void smokeTestIntersectionOfCipherSuites() throws NoSuchAlgorithmException {
+ DynamicSSLContext dynamicSSLContext = new DynamicSSLContext();
+ SSLServerSocketTestInstance testSSLServerSingleCipherSuite =
+ new SSLServerSocketTestInstance(RESOURCES + "default-server.keystore.jks", RESOURCES + "default-server.truststore.jks", 10004);
+ testSSLServerSingleCipherSuite.setConfiguredEnabledCipherSuites(new String[]{"TLS_RSA_WITH_AES_128_CBC_SHA256", "TLS_RSA_WITH_AES_256_CBC_SHA256"});
+ testSSLServerSingleCipherSuite.run();
+ AuthenticationContext context = getAuthenticationContext("wildfly-config-dynamic-ssl-test.xml");
+ context.run(() -> {
+ try {
+ SSLSocket clientSslSocket = (SSLSocket) dynamicSSLContext.getSocketFactory().createSocket();
+ SSLParameters sslParameters = clientSslSocket.getSSLParameters();
+ sslParameters.setCipherSuites(new String[]{"TLS_RSA_WITH_AES_256_CBC_SHA256"});
+ clientSslSocket.setSSLParameters(sslParameters);
+ clientSslSocket.connect(new InetSocketAddress("localhost", 10000));
+ clientSslSocket.startHandshake();
+ Assert.assertEquals("TLS_RSA_WITH_AES_256_CBC_SHA256", clientSslSocket.getSession().getCipherSuite());
+ checkOutputIsOK(clientSslSocket);
+ clientSslSocket.close();
+ } catch (Exception e) {
+ Assert.assertEquals("fine", e.getMessage());
+ }
+ });
+ }
+
+ @Test
+ public void testChangingAuthenticationContextsTest() throws NoSuchAlgorithmException {
+ DynamicSSLContext dynamicSSLContext = new DynamicSSLContext();
+ SSLSocketFactory socketFactory = dynamicSSLContext.getSocketFactory();
+
+ AuthenticationContext.empty().withSsl(MatchRule.ALL.matchPort(10001), () -> DynamicSSLTestUtils
+ .createSSLContext(RESOURCES + "client1.keystore.jks", RESOURCES + "client1.truststore.jks", "Elytron")).run(() -> {
+ try {
+ Socket clientSslSocket = socketFactory.createSocket("localhost", 10001);
+ checkOutputIsOK((SSLSocket) clientSslSocket);
+ clientSslSocket.close();
+ } catch (Exception e) {
+ Assert.assertEquals("fine", e.getMessage());
+ }
+ });
+
+ AuthenticationContext.empty().withSsl(MatchRule.ALL.matchPort(10002), () -> DynamicSSLTestUtils
+ .createSSLContext(RESOURCES + "client2.keystore.jks", RESOURCES + "client2.truststore.jks", "Elytron")).run(() -> {
+ try {
+ Socket clientSslSocket = socketFactory.createSocket("localhost", 10002);
+ checkOutputIsOK((SSLSocket) clientSslSocket);
+ clientSslSocket.close();
+ } catch (Exception e) {
+ Assert.assertEquals("fine", e.getMessage());
+ }
+ });
+
+ AuthenticationContext.empty().withSsl(MatchRule.ALL.matchPort(10003), () -> DynamicSSLTestUtils
+ .createSSLContext(RESOURCES + "client3.keystore.jks", RESOURCES + "client3.truststore.jks", "Elytron")).run(() -> {
+ try {
+ Socket clientSslSocket = socketFactory.createSocket("localhost", 10003);
+ checkOutputIsOK((SSLSocket) clientSslSocket);
+ clientSslSocket.close();
+ } catch (Exception e) {
+ Assert.assertEquals("fine", e.getMessage());
+ }
+ });
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testThrowAnExceptionWhenLoop() throws NoSuchAlgorithmException {
+
+ DynamicSSLContext dynamicSSLContext = new DynamicSSLContext();
+ SSLSocketFactory socketFactory = dynamicSSLContext.getSocketFactory();
+ SSLContext previousDefaultSSLContext = SSLContext.getDefault();
+ SSLContext.setDefault(dynamicSSLContext);
+ AuthenticationContext.empty().withSsl(MatchRule.ALL.matchPort(10000), () -> DynamicSSLTestUtils
+ .createSSLContext(RESOURCES + "client1.keystore.jks", RESOURCES + "client1.truststore.jks", "Elytron")).run(() -> {
+ try {
+ Socket clientSslSocket = socketFactory.createSocket("localhost", 12345);
+ checkOutputIsOK((SSLSocket) clientSslSocket);
+ clientSslSocket.close();
+ } catch (IOException e) {
+ Assert.assertEquals("fine", e.getMessage());
+ } finally {
+ SSLContext.setDefault(previousDefaultSSLContext);
+ }
+ });
+ }
+
+
+ @Test
+ public void testPreconfiguredDefault() throws NoSuchAlgorithmException {
+ DynamicSSLContext dynamicSSLContext = new DynamicSSLContext();
+ final AuthenticationContextConfigurationClient AUTH_CONTEXT_CLIENT =
+ AccessController.doPrivileged((PrivilegedAction) AuthenticationContextConfigurationClient::new);
+ try {
+
+ AuthenticationContext contextWithConfiguredDefault = getAuthenticationContext("wildfly-config-dynamic-ssl-test.xml");
+ AuthenticationContext contextWithoutConfiguredDefault = getAuthenticationContext("wildfly-config-dynamic-ssl-test-without-default-sslcontext.xml");
+
+ SSLContext preconfiguredDefault = AUTH_CONTEXT_CLIENT.getDefaultSSLContext(contextWithConfiguredDefault);
+ SSLContext jvmDefault = AUTH_CONTEXT_CLIENT.getDefaultSSLContext(contextWithoutConfiguredDefault);
+
+ Assert.assertEquals(jvmDefault, SSLContext.getDefault());
+
+ // AuthenticationContextConfigurationClient always creates new instances. So we can check that preconfigured SSLContext was received
+ // correctly by successful connection to the host and port that requires that ssl context.
+
+ // We first test configured default by using createSocket(host, port) with port not specified in any match rules.
+ // Second we use empty createSocket method that will later connect to the same host and port successfully.
+
+ contextWithConfiguredDefault.run(() -> {
+ try {
+ SSLSocket clientSslSocket1 = (SSLSocket) preconfiguredDefault.getSocketFactory().createSocket("localhost", 10000);
+ clientSslSocket1.setReuseAddress(true);
+ checkOutputIsOK(clientSslSocket1);
+ clientSslSocket1.close();
+ //preconfigured default will be used to create socket since no host and port was provided
+ SSLSocket clientSocketWithDynamicDefaultSSLContext = (SSLSocket) dynamicSSLContext.getSocketFactory().createSocket();
+ clientSocketWithDynamicDefaultSSLContext.setUseClientMode(true);
+ // configured default is the one which passes for this host and port
+ clientSocketWithDynamicDefaultSSLContext.connect(new InetSocketAddress("localhost", 10000));
+ checkOutputIsOK(clientSocketWithDynamicDefaultSSLContext);
+ clientSocketWithDynamicDefaultSSLContext.close();
+ } catch (Exception e) {
+ Assert.assertEquals("fine", e.getMessage());
+ }
+ }
+ );
+
+ } catch (Exception e) {
+ Assert.assertEquals("fine", e.getMessage());
+ }
+ }
+
+ private void checkOutputIsOK(SSLSocket clientSslSocket) throws IOException {
+ PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(clientSslSocket.getOutputStream()));
+ printWriter.println("Client Hello");
+ printWriter.flush();
+ InputStream inputStream = clientSslSocket.getInputStream();
+ BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
+ String line = bufferedReader.readLine().trim();
+ Assert.assertEquals(STATUS_OK, line);
+ }
+
+ private AuthenticationContext getAuthenticationContext(String path) {
+ return doPrivileged((PrivilegedAction) () -> {
+ URL config = getClass().getResource(path);
+ try {
+ return ElytronXmlParser.parseAuthenticationClientConfiguration(config.toURI()).create();
+ } catch (Exception e) {
+ Assert.assertEquals("fine", e.getMessage());
+ throw new InvalidAuthenticationConfigurationException(e);
+ }
+ });
+ }
+}
diff --git a/dynamic-ssl/src/test/java/org/wildfly/security/dynamic/ssl/DynamicSSLTestUtils.java b/dynamic-ssl/src/test/java/org/wildfly/security/dynamic/ssl/DynamicSSLTestUtils.java
new file mode 100644
index 00000000000..1f60cbdc64a
--- /dev/null
+++ b/dynamic-ssl/src/test/java/org/wildfly/security/dynamic/ssl/DynamicSSLTestUtils.java
@@ -0,0 +1,185 @@
+/*
+ * 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.dynamic.ssl;
+
+import org.junit.Assert;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.security.auth.x500.X500Principal;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import org.wildfly.security.ssl.test.util.CAGenerationTool;
+import org.wildfly.security.x500.cert.X509CertificateExtension;
+
+/**
+ * Utility class for DynamicSSLContextTest class.
+ *
+ * @author Diana Krepinska (Vilkolakova)
+ */
+public class DynamicSSLTestUtils {
+
+ private static final String CLIENT_ALIAS = "client";
+ private static final String LOCALHOST_ALIAS = "localhost";
+ private static final String KEYSTORE_TYPE = "JKS";
+ private static final String TLS_PROTOCOL_VERSION = "TLSv1.2";
+ public static final String KEY_MANAGER_FACTORY_ALGORITHM = "SunX509";
+ private static char[] PASSWORD = "Elytron".toCharArray();
+ private static File KEYSTORES_DIR = new File("./target/keystores");
+
+ private static String CLIENT1_KEYSTORE_FILENAME = "client1.keystore.jks";
+ private static String CLIENT1_TRUSTSTORE_FILENAME ="client1.truststore.jks";
+ private static String SERVER1_KEYSTORE_FILENAME = "server1.keystore.jks";
+ private static String SERVER1_TRUSTSTORE_FILENAME = "server1.truststore.jks";
+
+ private static String CLIENT2_KEYSTORE_FILENAME = "client2.keystore.jks";
+ private static String CLIENT2_TRUSTSTORE_FILENAME ="client2.truststore.jks";
+ private static String SERVER2_KEYSTORE_FILENAME = "server2.keystore.jks";
+ private static String SERVER2_TRUSTSTORE_FILENAME = "server2.truststore.jks";
+
+ private static String CLIENT3_KEYSTORE_FILENAME = "client3.keystore.jks";
+ private static String CLIENT3_TRUSTSTORE_FILENAME ="client3.truststore.jks";
+ private static String SERVER3_KEYSTORE_FILENAME = "server3.keystore.jks";
+ private static String SERVER3_TRUSTSTORE_FILENAME = "server3.truststore.jks";
+
+ private static String DEFAULT_CLIENT_KEYSTORE_FILENAME = "default-client.keystore.jks";
+ private static String DEFAULT_CLIENT_TRUSTSTORE_FILENAME ="default-client.truststore.jks";
+ private static String DEFAULT_SERVER_KEYSTORE_FILENAME = "default-server.keystore.jks";
+ private static String DEFAULT_SERVER_TRUSTSTORE_FILENAME = "default-server.truststore.jks";
+
+ static SSLContext createSSLContext(String keystorePath, String truststorePath, String password) {
+ try {
+ KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE);
+ keyStore.load(new FileInputStream(keystorePath), password.toCharArray());
+
+ // Create key manager
+ KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KEY_MANAGER_FACTORY_ALGORITHM);
+ keyManagerFactory.init(keyStore, password.toCharArray());
+ KeyManager[] km = keyManagerFactory.getKeyManagers();
+
+ KeyStore trustStore = KeyStore.getInstance(KEYSTORE_TYPE);
+ trustStore.load(new FileInputStream(truststorePath), password.toCharArray());
+
+ // Create trust manager
+ TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(KEY_MANAGER_FACTORY_ALGORITHM);
+ trustManagerFactory.init(trustStore);
+ TrustManager[] tm = trustManagerFactory.getTrustManagers();
+
+ // Initialize SSLContext
+ SSLContext sslContext = SSLContext.getInstance(TLS_PROTOCOL_VERSION);
+ sslContext.init(km, tm, null);
+
+ return sslContext;
+ } catch (Exception ex) {
+ Assert.fail();
+ }
+ return null;
+ }
+
+ static void createKeystores() throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
+ if (!KEYSTORES_DIR.exists()) {
+ KEYSTORES_DIR.mkdirs();
+ }
+
+ generateTwoWaySSLKeystoresAndTruststores(CLIENT1_KEYSTORE_FILENAME, SERVER1_KEYSTORE_FILENAME, CLIENT1_TRUSTSTORE_FILENAME, SERVER1_TRUSTSTORE_FILENAME);
+ generateTwoWaySSLKeystoresAndTruststores(CLIENT2_KEYSTORE_FILENAME, SERVER2_KEYSTORE_FILENAME, CLIENT2_TRUSTSTORE_FILENAME, SERVER2_TRUSTSTORE_FILENAME);
+ generateTwoWaySSLKeystoresAndTruststores(CLIENT3_KEYSTORE_FILENAME, SERVER3_KEYSTORE_FILENAME, CLIENT3_TRUSTSTORE_FILENAME, SERVER3_TRUSTSTORE_FILENAME);
+ generateTwoWaySSLKeystoresAndTruststores(DEFAULT_CLIENT_KEYSTORE_FILENAME, DEFAULT_SERVER_KEYSTORE_FILENAME, DEFAULT_CLIENT_TRUSTSTORE_FILENAME, DEFAULT_SERVER_TRUSTSTORE_FILENAME);
+ }
+
+ private static void generateTwoWaySSLKeystoresAndTruststores(String clientKeystoreFilename, String serverKeystoreFilename,
+ String clientTruststoreFilename, String serverTruststoreFilename) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
+ CAGenerationTool caGenerationTool = null;
+ try {
+ caGenerationTool = CAGenerationTool.builder()
+ .setBaseDir(KEYSTORES_DIR.getCanonicalPath())
+ .setRequestIdentities(CAGenerationTool.Identity.values())
+ .build();
+ } catch(Exception e) {
+ e.printStackTrace();
+ Assert.fail();
+ }
+
+ // Generates client certificate
+ X509Certificate clientCertificate = caGenerationTool.createIdentity(CLIENT_ALIAS,
+ new X500Principal("OU=Elytron"),
+ clientKeystoreFilename,
+ CAGenerationTool.Identity.CA,
+ new X509CertificateExtension[]{});
+
+ // Generates server certificate
+ X509Certificate serverCertificate = caGenerationTool.createIdentity(LOCALHOST_ALIAS,
+ new X500Principal("OU=Elytron"),
+ serverKeystoreFilename,
+ CAGenerationTool.Identity.CA,
+ new X509CertificateExtension[]{});
+
+ // create truststores
+ KeyStore clientTrustStore = KeyStore.getInstance(KEYSTORE_TYPE);
+ clientTrustStore.load(null, null);
+
+ KeyStore serverTrustStore = KeyStore.getInstance(KEYSTORE_TYPE);
+ serverTrustStore.load(null, null);
+
+ clientTrustStore.setCertificateEntry(LOCALHOST_ALIAS, serverCertificate);
+ serverTrustStore.setCertificateEntry(CLIENT_ALIAS, clientCertificate);
+
+ File clientTrustFile = new File(KEYSTORES_DIR, clientTruststoreFilename);
+ try (FileOutputStream clientStream = new FileOutputStream(clientTrustFile)) {
+ clientTrustStore.store(clientStream, PASSWORD);
+ }
+
+ File serverTrustFile = new File(KEYSTORES_DIR, serverTruststoreFilename);
+ try (FileOutputStream serverStream = new FileOutputStream(serverTrustFile)) {
+ serverTrustStore.store(serverStream, PASSWORD);
+ }
+ }
+
+ public static void deleteKeystores() {
+ new File(KEYSTORES_DIR, CLIENT1_KEYSTORE_FILENAME).delete();
+ new File(KEYSTORES_DIR, CLIENT1_TRUSTSTORE_FILENAME).delete();
+ new File(KEYSTORES_DIR, CLIENT2_KEYSTORE_FILENAME).delete();
+ new File(KEYSTORES_DIR, CLIENT2_TRUSTSTORE_FILENAME).delete();
+ new File(KEYSTORES_DIR, CLIENT3_KEYSTORE_FILENAME).delete();
+ new File(KEYSTORES_DIR, CLIENT3_TRUSTSTORE_FILENAME).delete();
+ new File(KEYSTORES_DIR, DEFAULT_CLIENT_KEYSTORE_FILENAME).delete();
+ new File(KEYSTORES_DIR, DEFAULT_CLIENT_TRUSTSTORE_FILENAME).delete();
+ new File(KEYSTORES_DIR, SERVER1_KEYSTORE_FILENAME).delete();
+ new File(KEYSTORES_DIR, SERVER1_TRUSTSTORE_FILENAME).delete();
+ new File(KEYSTORES_DIR, SERVER2_KEYSTORE_FILENAME).delete();
+ new File(KEYSTORES_DIR, SERVER2_TRUSTSTORE_FILENAME).delete();
+ new File(KEYSTORES_DIR, SERVER3_KEYSTORE_FILENAME).delete();
+ new File(KEYSTORES_DIR, SERVER3_TRUSTSTORE_FILENAME).delete();
+ new File(KEYSTORES_DIR, DEFAULT_SERVER_KEYSTORE_FILENAME).delete();
+ new File(KEYSTORES_DIR, DEFAULT_SERVER_TRUSTSTORE_FILENAME).delete();
+ KEYSTORES_DIR.delete();
+ }
+}
diff --git a/dynamic-ssl/src/test/java/org/wildfly/security/dynamic/ssl/SSLServerSocketTestInstance.java b/dynamic-ssl/src/test/java/org/wildfly/security/dynamic/ssl/SSLServerSocketTestInstance.java
new file mode 100644
index 00000000000..b69715f1437
--- /dev/null
+++ b/dynamic-ssl/src/test/java/org/wildfly/security/dynamic/ssl/SSLServerSocketTestInstance.java
@@ -0,0 +1,138 @@
+/*
+ * 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.dynamic.ssl;
+
+import okhttp3.TlsVersion;
+import org.junit.Assert;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.SSLSocket;
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.net.InetSocketAddress;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Utility class for running SSLServerSocket instance for testing.
+ *
+ * @author Diana Krepinska (Vilkolakova)
+ */
+public class SSLServerSocketTestInstance {
+
+ private int port;
+ private String keystorePath;
+ private String truststorePath;
+ private String[] configuredEnabledCipherSuites;
+ private SSLServerSocket sslServerSocket;
+ private AtomicBoolean running = new AtomicBoolean(false);
+ private Thread serverThread;
+
+ public SSLServerSocketTestInstance(String pathToKeystore, String pathToTruststore, int port) {
+ this.keystorePath = pathToKeystore;
+ this.truststorePath = pathToTruststore;
+ this.port = port;
+ }
+
+ void setConfiguredEnabledCipherSuites(String[] configuredEnabledCipherSuite) {
+ this.configuredEnabledCipherSuites = configuredEnabledCipherSuite;
+ }
+
+ public void run() {
+ String password = "Elytron";
+ SSLContext sslContext = DynamicSSLTestUtils.createSSLContext(this.keystorePath, this.truststorePath, password);
+ try {
+ SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory();
+ sslServerSocket = (javax.net.ssl.SSLServerSocket) sslServerSocketFactory.createServerSocket();
+ sslServerSocket.setNeedClientAuth(true);
+ sslServerSocket.setUseClientMode(false);
+ sslServerSocket.setWantClientAuth(true);
+ sslServerSocket.setEnabledProtocols(new String[]{
+ TlsVersion.TLS_1_2.javaName(),
+ TlsVersion.TLS_1_3.javaName()
+ });
+ if (configuredEnabledCipherSuites != null) {
+ sslServerSocket.setEnabledCipherSuites(configuredEnabledCipherSuites);
+ }
+ sslServerSocket.bind(new InetSocketAddress("localhost", port));
+ serverThread = new Thread(() -> {
+ running.set(true);
+ while (running.get()) {
+ SSLSocket sslSocket;
+ try {
+ sslSocket = (SSLSocket) sslServerSocket.accept();
+ new Thread(new ServerThread(sslSocket)).start();
+ } catch (Exception e) {
+ Assert.fail();
+ }
+ }
+ });
+ serverThread.start();
+ } catch (Exception ex) {
+ Assert.fail();
+ } finally {
+ running.set(false);
+ }
+ }
+
+ public void stop() {
+ running.set(false);
+ }
+
+ // Thread handling the socket from client
+ public static class ServerThread implements Runnable {
+ public static final String STATUS_OK = "HTTP/1.1 200 OK";
+ private SSLSocket sslSocket;
+ AtomicBoolean running = new AtomicBoolean(false);
+
+ ServerThread(SSLSocket sslSocket) {
+ this.sslSocket = sslSocket;
+ }
+
+ public void run() {
+ try {
+ // wait for client's message first so that the first client message will trigger handshake.
+ // This way client can set its preferences in SSLParams after creation of bound createSocket(host,port) without server triggering handshake before.
+ running.set(true);
+ sslSocket.startHandshake();
+ InputStream inputStream = sslSocket.getInputStream();
+ BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
+ while (running.get()) {
+ if ((bufferedReader.readLine()).equals("Client Hello")) {
+ break;
+ }
+ }
+ // if successful return 200
+ PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(sslSocket.getOutputStream()));
+ printWriter.println(STATUS_OK);
+ printWriter.flush();
+ sslSocket.close();
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ Assert.fail();
+ } finally {
+ running.set(false);
+ }
+ }
+ }
+}
diff --git a/dynamic-ssl/src/test/resources/org/wildfly/security/dynamic/ssl/wildfly-config-dynamic-ssl-test-without-default-sslcontext.xml b/dynamic-ssl/src/test/resources/org/wildfly/security/dynamic/ssl/wildfly-config-dynamic-ssl-test-without-default-sslcontext.xml
new file mode 100644
index 00000000000..4bfe9365125
--- /dev/null
+++ b/dynamic-ssl/src/test/resources/org/wildfly/security/dynamic/ssl/wildfly-config-dynamic-ssl-test-without-default-sslcontext.xml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dynamic-ssl/src/test/resources/org/wildfly/security/dynamic/ssl/wildfly-config-dynamic-ssl-test.xml b/dynamic-ssl/src/test/resources/org/wildfly/security/dynamic/ssl/wildfly-config-dynamic-ssl-test.xml
new file mode 100644
index 00000000000..e857cbb9d08
--- /dev/null
+++ b/dynamic-ssl/src/test/resources/org/wildfly/security/dynamic/ssl/wildfly-config-dynamic-ssl-test.xml
@@ -0,0 +1,114 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/encryption/pom.xml b/encryption/pom.xml
index 52c3cf2f704..de9ed3fea5f 100644
--- a/encryption/pom.xml
+++ b/encryption/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
4.0.0
diff --git a/http/base/pom.xml b/http/base/pom.xml
index dca3c82da80..5fb11808f14 100644
--- a/http/base/pom.xml
+++ b/http/base/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/http/basic/pom.xml b/http/basic/pom.xml
index 316fb0c6e5a..382d0f54c79 100644
--- a/http/basic/pom.xml
+++ b/http/basic/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/http/bearer/pom.xml b/http/bearer/pom.xml
index 7c3c0c4312c..c7ca60a5060 100644
--- a/http/bearer/pom.xml
+++ b/http/bearer/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
@@ -68,6 +68,11 @@
org.wildfly.common
wildfly-common
+
+ junit
+ junit
+ test
+
diff --git a/http/bearer/src/test/java/org/wildfly/security/http/bearer/BearerMechanismFactoryTest.java b/http/bearer/src/test/java/org/wildfly/security/http/bearer/BearerMechanismFactoryTest.java
new file mode 100644
index 00000000000..08c09f0dc90
--- /dev/null
+++ b/http/bearer/src/test/java/org/wildfly/security/http/bearer/BearerMechanismFactoryTest.java
@@ -0,0 +1,127 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2023 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.wildfly.security.http.bearer;
+
+
+import org.junit.Test;
+import org.junit.Assert;
+import org.wildfly.security.http.HttpAuthenticationException;
+import org.wildfly.security.http.HttpServerAuthenticationMechanism;
+
+import javax.security.auth.callback.CallbackHandler;
+import java.util.HashMap;
+
+import static org.wildfly.security.http.HttpConstants.BASIC_NAME;
+import static org.wildfly.security.http.HttpConstants.BEARER_TOKEN;
+
+/**
+ * This test class contains unit tests for the {@link BearerMechanismFactory}.
+ *
+ * @author Marek Jusko
+ */
+public class BearerMechanismFactoryTest {
+
+ private final BearerMechanismFactory bearerMechanismFactory = new BearerMechanismFactory();
+ private final HashMap emptyProperties = new HashMap<>();
+
+ CallbackHandler dummyCallbackHandler = callbacks -> {};
+
+ /**
+ * Unit test for the {@link BearerMechanismFactory#getMechanismNames} method with a {@code null} properties map.
+ * Verifies that the method returns a non-null array containing the Bearer mechanism name.
+ */
+ @Test
+ public void testGetMechanismNamesWithNullProperties() {
+ BearerMechanismFactory factory = new BearerMechanismFactory();
+ String[] mechanismNames = factory.getMechanismNames(null);
+
+ Assert.assertNotNull("Array of mechanism names cannot be null.", mechanismNames);
+ Assert.assertEquals(1, mechanismNames.length);
+ Assert.assertEquals(BEARER_TOKEN, mechanismNames[0]);
+ }
+
+ /**
+ * Unit test for the {@link BearerMechanismFactory#getMechanismNames} method with an empty properties map.
+ * Verifies that the method returns a non-null array containing the Bearer mechanism name.
+ */
+ @Test
+ public void testGetMechanismNamesWithEmptyProperties() {
+ BearerMechanismFactory factory = new BearerMechanismFactory();
+ String[] mechanismNames = factory.getMechanismNames(emptyProperties);
+
+ Assert.assertNotNull("Array of mechanism names cannot be null.", mechanismNames);
+ Assert.assertEquals(1, mechanismNames.length);
+ Assert.assertEquals(BEARER_TOKEN, mechanismNames[0]);
+ }
+
+ /**
+ * Verifies that creating an authentication mechanism with a null mechanism name results in an IllegalArgumentException.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testCreateAuthenticationMechanismMechanismNameNull() throws HttpAuthenticationException {
+ bearerMechanismFactory.createAuthenticationMechanism(null, emptyProperties, dummyCallbackHandler);
+ Assert.fail("IllegalArgumentException expected for null mechanismName.");
+ }
+
+ /**
+ * Verifies that creating an authentication mechanism with null properties results in an IllegalArgumentException.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testCreateAuthenticationMechanismPropertiesNull() throws HttpAuthenticationException {
+ bearerMechanismFactory.createAuthenticationMechanism(BEARER_TOKEN, null, dummyCallbackHandler);
+ Assert.fail("IllegalArgumentException expected for null properties.");
+ }
+
+ /**
+ * Verifies that creating an authentication mechanism with a null callback handler results in an IllegalArgumentException.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testCreateAuthenticationMechanismCallbackHandlerNull() throws HttpAuthenticationException {
+ bearerMechanismFactory.createAuthenticationMechanism(BEARER_TOKEN, emptyProperties, null);
+ Assert.fail("IllegalArgumentException expected for null callbackHandler.");
+ }
+
+ /**
+ * Verifies that creating an authentication mechanism with the BASIC mechanism name returns null.
+ */
+ @Test
+ public void testCreateAuthenticationMechanismBasicMechanismName() throws HttpAuthenticationException {
+ HttpServerAuthenticationMechanism mechanism = bearerMechanismFactory.createAuthenticationMechanism(BASIC_NAME, emptyProperties, dummyCallbackHandler);
+ Assert.assertNull("Expected null mechanism for the BASIC mechanism name.", mechanism);
+ }
+
+ /**
+ * Verifies that creating an authentication mechanism with an incorrect mechanism name returns null.
+ */
+ @Test
+ public void testCreateAuthenticationMechanismIncorrectMechanismName() throws HttpAuthenticationException {
+ HttpServerAuthenticationMechanism mechanism = bearerMechanismFactory.createAuthenticationMechanism("INCORRECT_NAME", emptyProperties, dummyCallbackHandler);
+ Assert.assertNull("Expected null mechanism for an incorrect mechanism name.", mechanism);
+ }
+
+ /**
+ * Tests that creating a Bearer authentication mechanism with valid parameters returns a non-null mechanism.
+ */
+ @Test
+ public void testCreateValidBearerAuthenticationMechanism() throws HttpAuthenticationException{
+ HttpServerAuthenticationMechanism httpServerAuthenticationMechanism = bearerMechanismFactory.createAuthenticationMechanism(BEARER_TOKEN, emptyProperties, dummyCallbackHandler);
+ Assert.assertNotNull("HttpServerAuthenticationMechanism cannot be null.",httpServerAuthenticationMechanism);
+ }
+
+}
diff --git a/http/cert/pom.xml b/http/cert/pom.xml
index 7b68887af5f..669a855053d 100644
--- a/http/cert/pom.xml
+++ b/http/cert/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
@@ -71,7 +71,19 @@
org.wildfly.common
wildfly-common
-
+
+
+
+
+ org.jmockit
+ jmockit
+ test
+
+
+ junit
+ junit
+ test
+
diff --git a/http/cert/src/test/java/org.wildfly.security.http.cert/ClientCertAuthenticationMechanismFactoryTest.java b/http/cert/src/test/java/org.wildfly.security.http.cert/ClientCertAuthenticationMechanismFactoryTest.java
new file mode 100644
index 00000000000..041ac99e7a5
--- /dev/null
+++ b/http/cert/src/test/java/org.wildfly.security.http.cert/ClientCertAuthenticationMechanismFactoryTest.java
@@ -0,0 +1,124 @@
+/*
+ * 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.cert;
+
+import org.wildfly.security.http.HttpAuthenticationException;
+import org.wildfly.security.http.HttpServerAuthenticationMechanism;
+import org.wildfly.security.http.HttpServerAuthenticationMechanismFactory;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ClientCertAuthenticationMechanismFactoryTest {
+ private HttpServerAuthenticationMechanismFactory clientCertMechanismFactory = new ClientCertMechanismFactory();
+
+ CallbackHandler dummyCallbackHandler = new CallbackHandler() {
+ @Override
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ }
+ };
+
+ /**
+ * Tests that {@link ServerMechanismFactoryImpl#getMechanismNames(Map)} correctly
+ * handles null or empty properties parameter as possible value.
+ */
+ @Test
+ public void testGetMechanismNamesPropertiesNull() {
+ clientCertMechanismFactory.getMechanismNames(null);
+ clientCertMechanismFactory.getMechanismNames(new HashMap());
+ }
+
+ /**
+ * Tests that {@link ServerMechanismFactoryImpl#getMechanismNames(Map)} does not return null.
+ */
+ @Test
+ public void testGetMechanismNamesReturnNotNull() {
+ String[] mechanismNames = clientCertMechanismFactory.getMechanismNames(null);
+ Assert.assertNotNull("Array of mechanism names is not null.", mechanismNames);
+ }
+
+ /**
+ * Tests that {@link ServerMechanismFactoryImpl#createAuthenticationMechanism(String, Map, javax.security.auth.callback.CallbackHandler)}
+ * does handle null mechanism name parameter correctly - does not allow.
+ * @throws HttpAuthenticationException
+ */
+ @Test
+ public void testCreateAuthenticationMechanismMechanismNameNull() throws HttpAuthenticationException {
+ try {
+ clientCertMechanismFactory.createAuthenticationMechanism(null, new HashMap(), dummyCallbackHandler);
+ Assert.fail("Mechanism name could not be null");
+ } catch (IllegalArgumentException e) {
+ // OK - expected exception state
+ }
+ }
+
+ /**
+ * Tests that {@link ServerMechanismFactoryImpl#createAuthenticationMechanism(String, Map, javax.security.auth.callback.CallbackHandler)}
+ * does handle null properties parameter correctly - does not allow.
+ */
+ @Test
+ public void testCreateAuthenticationMechanismPropertiesNull() throws HttpAuthenticationException {
+ try {
+ clientCertMechanismFactory.createAuthenticationMechanism("CLIENT_CERT", null, dummyCallbackHandler);
+ Assert.fail("Properties could not be null");
+ } catch (IllegalArgumentException e) {
+ // OK - expected exception state
+ }
+ }
+
+ /**
+ * Tests that {@link ServerMechanismFactoryImpl#createAuthenticationMechanism(String, Map, javax.security.auth.callback.CallbackHandler)}
+ * does handle wrong mechanism ("BASIC") - returns null.
+ */
+ @Test
+ public void testCreateAuthenticationMechanismBasicMechanismName() throws HttpAuthenticationException{
+ HttpServerAuthenticationMechanism httpServerAuthenticationMechanism = clientCertMechanismFactory.createAuthenticationMechanism("BASIC",new HashMap(),dummyCallbackHandler);
+ Assert.assertNull("Provided mechanism must be null.", httpServerAuthenticationMechanism);
+ }
+
+ /**
+ * Tests that {@link ServerMechanismFactoryImpl#createAuthenticationMechanism(String, Map, javax.security.auth.callback.CallbackHandler)}
+ * does handle null properties parameter correctly - does not allow.
+ */
+ @Test
+ public void testCreateAuthenticationMechanismCallbackHandlerNull() throws HttpAuthenticationException {
+ try {
+ clientCertMechanismFactory.createAuthenticationMechanism("CLIENT_CERT", new HashMap(), null);
+ Assert.fail("CallbackHandler could not be null");
+ } catch (IllegalArgumentException e) {
+ // OK - expected exception state
+ }
+ }
+
+ /**
+ * Tests that {@link ServerMechanismFactoryImpl#createAuthenticationMechanism(String, Map, javax.security.auth.callback.CallbackHandler)}
+ * does handle wrong mechanism name correctly - returns null.
+ */
+ @Test
+ public void testCreateAuthenticationMechanismWrongMechanismName() throws HttpAuthenticationException {
+ HttpServerAuthenticationMechanism httpServerAuthenticationMechanism = clientCertMechanismFactory.createAuthenticationMechanism("MECHANISM_NAME_DOES_NOT_EXISTS", new HashMap(), dummyCallbackHandler);
+ Assert.assertNull("Provided mechanism must be null.", httpServerAuthenticationMechanism);
+ }
+}
diff --git a/http/deprecated/pom.xml b/http/deprecated/pom.xml
index 3a104a68875..190c1e7adc2 100644
--- a/http/deprecated/pom.xml
+++ b/http/deprecated/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/http/digest/pom.xml b/http/digest/pom.xml
index 552e00155d7..8f6d77deb59 100644
--- a/http/digest/pom.xml
+++ b/http/digest/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
@@ -76,6 +76,18 @@
org.wildfly.common
wildfly-common
+
+
+
+ org.jmockit
+ jmockit
+ test
+
+
+ junit
+ junit
+ test
+
diff --git a/http/digest/src/main/java/org/wildfly/security/http/digest/DigestAuthenticationMechanism.java b/http/digest/src/main/java/org/wildfly/security/http/digest/DigestAuthenticationMechanism.java
index 7d7e2f4f7db..97f2a53a857 100644
--- a/http/digest/src/main/java/org/wildfly/security/http/digest/DigestAuthenticationMechanism.java
+++ b/http/digest/src/main/java/org/wildfly/security/http/digest/DigestAuthenticationMechanism.java
@@ -23,6 +23,7 @@
import static org.wildfly.security.http.HttpConstants.AUTHORIZATION;
import static org.wildfly.security.http.HttpConstants.BAD_REQUEST;
import static org.wildfly.security.http.HttpConstants.CNONCE;
+import static org.wildfly.security.http.HttpConstants.DIGEST_NAME;
import static org.wildfly.security.http.HttpConstants.NC;
import static org.wildfly.security.http.HttpConstants.QOP;
import static org.wildfly.security.http.HttpConstants.URI;
@@ -49,6 +50,7 @@
import java.security.Provider;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
import java.util.function.Supplier;
import javax.security.auth.callback.Callback;
@@ -326,10 +328,19 @@ private byte[] calculateResponseDigest(MessageDigest messageDigest, byte[] hA1,
}
private byte[] getH_A1(final MessageDigest messageDigest, final String username, final String messageRealm) throws AuthenticationMechanismException {
- PasswordDigestObtainer obtainer = new PasswordDigestObtainer(callbackHandler, username, messageRealm, httpDigest, DigestPassword.ALGORITHM_DIGEST_MD5, messageDigest, providers, null, true, false);
+ PasswordDigestObtainer obtainer = new PasswordDigestObtainer(callbackHandler, username, messageRealm, httpDigest, getCredentialAlgorithm(getMechanismName()), messageDigest, providers, null, true, false);
return obtainer.handleUserRealmPasswordCallbacks();
}
+ private String getCredentialAlgorithm(String mechanismName) {
+ switch (mechanismName) {
+ case DIGEST_NAME:
+ return DigestPassword.ALGORITHM_DIGEST_MD5;
+ default:
+ return mechanismName.toLowerCase(Locale.ROOT);
+ }
+ }
+
private String convertToken(final String name, final byte[] value) throws AuthenticationMechanismException {
if (value == null) {
throw httpDigest.mechMissingDirective(name);
diff --git a/http/digest/src/main/java/org/wildfly/security/http/digest/DigestMechanismFactory.java b/http/digest/src/main/java/org/wildfly/security/http/digest/DigestMechanismFactory.java
index 85ba265ba63..3a64c3fe130 100644
--- a/http/digest/src/main/java/org/wildfly/security/http/digest/DigestMechanismFactory.java
+++ b/http/digest/src/main/java/org/wildfly/security/http/digest/DigestMechanismFactory.java
@@ -109,8 +109,9 @@ public HttpServerAuthenticationMechanism createAuthenticationMechanism(String me
return new DigestAuthenticationMechanism(callbackHandler, nonceManager, (String) properties.get(CONFIG_REALM), (String) properties.get(CONFIG_CONTEXT_PATH), DIGEST_SHA256_NAME, SHA256, providers, (String) properties.get(HttpConstants.CONFIG_VALIDATE_DIGEST_URI));
case DIGEST_SHA512_256_NAME:
return new DigestAuthenticationMechanism(callbackHandler, nonceManager, (String) properties.get(CONFIG_REALM), (String) properties.get(CONFIG_CONTEXT_PATH), DIGEST_SHA512_256_NAME, SHA512_256, providers, (String) properties.get(HttpConstants.CONFIG_VALIDATE_DIGEST_URI));
+ default:
+ return null;
}
- return null;
}
/*
diff --git a/http/digest/src/test/java/org/wildfly/security/http/digest/DigestMechanismFactoryTest.java b/http/digest/src/test/java/org/wildfly/security/http/digest/DigestMechanismFactoryTest.java
new file mode 100644
index 00000000000..307bff709b7
--- /dev/null
+++ b/http/digest/src/test/java/org/wildfly/security/http/digest/DigestMechanismFactoryTest.java
@@ -0,0 +1,142 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2023 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.wildfly.security.http.digest;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.wildfly.security.http.HttpAuthenticationException;
+import org.wildfly.security.http.HttpServerAuthenticationMechanism;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import java.io.IOException;
+import java.util.HashMap;
+
+/**
+ * Tests of DigestMechanismFactory Class.
+ *
+ * @author Keshav Kumar
+ */
+
+public class DigestMechanismFactoryTest {
+
+ private DigestMechanismFactory digestMechanismFactory = new DigestMechanismFactory();
+
+ CallbackHandler dummyCallbackHandler = new CallbackHandler() {
+ @Override
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ }
+ };
+
+ /**
+ * Tests that getMechanismNames(Map) correctly
+ * handles null or empty properties parameter as possible value.
+ */
+ @Test
+ public void testGetMechanismNamesPropertiesNull(){
+ String[] mechanismNames1 = digestMechanismFactory.getMechanismNames(null);
+ Assert.assertNotNull("Array of mechanism names cannot be null.",mechanismNames1);
+
+ String[] mechanismNames2 = digestMechanismFactory.getMechanismNames(new HashMap());
+ Assert.assertNotNull("Array of mechanism names cannot be null.",mechanismNames2);
+ }
+
+ /**
+ * Tests that getMechanismNames(Map) does not return null.
+ */
+ @Test
+ public void testGetMechanismNamesReturnNotNull(){
+ String[] mechanismNames = digestMechanismFactory.getMechanismNames(null);
+ Assert.assertNotNull("Array of mechanism names cannot be null.", mechanismNames);
+ }
+
+ /**
+ * Tests that createAuthenticationMechanism(String, Map, javax.security.auth.callback.CallbackHandler)
+ * does handle null mechanism name parameter correctly - does not allow.
+ * @throws HttpAuthenticationException
+ */
+ @Test
+ public void testCreateAuthenticationMechanismMechanismNameNull() throws HttpAuthenticationException{
+ try {
+ digestMechanismFactory.createAuthenticationMechanism(null,new HashMap(),dummyCallbackHandler);
+ Assert.fail("Mechanism name could not be null");
+ }catch (IllegalArgumentException illegalArgumentException){
+ // OK - expected exception state
+ }
+ }
+
+ /**
+ * Tests that {createAuthenticationMechanism(String, Map, javax.security.auth.callback.CallbackHandler)
+ * does handle null properties parameter correctly - does not allow.
+ */
+ @Test
+ public void testCreateAuthenticationMechanismPropertiesNull() throws HttpAuthenticationException{
+ try {
+ digestMechanismFactory.createAuthenticationMechanism("DIGEST",null,dummyCallbackHandler);
+ Assert.fail("Properties could not be null");
+ }catch (IllegalArgumentException illegalArgumentException){
+ // OK - expected exception state
+ }
+ }
+
+ /**
+ * Tests that createAuthenticationMechanism(String, Map, javax.security.auth.callback.CallbackHandler)
+ * does handle null callbackHandler parameter correctly - does not allow.
+ */
+ @Test
+ public void testCreateAuthenticationMechanismCallbackHandlerNull() throws HttpAuthenticationException{
+ try {
+ digestMechanismFactory.createAuthenticationMechanism("DIGEST",new HashMap(),null);
+ Assert.fail("CallbackHandler could not be null");
+ }catch (IllegalArgumentException illegalArgumentException){
+ // OK - expected exception state
+ }
+ }
+
+ /**
+ * Tests that createAuthenticationMechanism(String, Map, javax.security.auth.callback.CallbackHandler)
+ * does handle wrong mechanism ("BASIC") - returns null.
+ */
+ @Test
+ public void testCreateAuthenticationMechanismBasicMechanismName() throws HttpAuthenticationException{
+ HttpServerAuthenticationMechanism httpServerAuthenticationMechanism = digestMechanismFactory.createAuthenticationMechanism("BASIC",new HashMap(),dummyCallbackHandler);
+ Assert.assertNull("Provided mechanism must be null.", httpServerAuthenticationMechanism);
+ }
+
+ /**
+ * Tests that createAuthenticationMechanism(String, Map, javax.security.auth.callback.CallbackHandler)
+ * does handle all not null parameter correctly - does not return null.
+ */
+ @Test
+ public void testCreateAuthenticationMechanismReturnNotNull() throws HttpAuthenticationException{
+ HttpServerAuthenticationMechanism httpServerAuthenticationMechanism = digestMechanismFactory.createAuthenticationMechanism("DIGEST",new HashMap(),dummyCallbackHandler);
+ Assert.assertNotNull("HttpServerAuthenticationMechanism cannot be null.",httpServerAuthenticationMechanism);
+ }
+
+ /**
+ * Tests that createAuthenticationMechanism(String, Map, javax.security.auth.callback.CallbackHandler)
+ * does handle wrong mechanism name correctly - returns null.
+ */
+ @Test
+ public void testCreateAuthenticationMechanismWrongMechanismName() throws HttpAuthenticationException{
+ HttpServerAuthenticationMechanism httpServerAuthenticationMechanism = digestMechanismFactory.createAuthenticationMechanism("MECHANISM_NAME_DOES_NOT_EXISTS",new HashMap(),dummyCallbackHandler);
+ Assert.assertNull("Provided mechanism must be null.", httpServerAuthenticationMechanism);
+ }
+}
diff --git a/http/external/pom.xml b/http/external/pom.xml
index 8fabf72fb42..3951119cd99 100644
--- a/http/external/pom.xml
+++ b/http/external/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/http/external/src/main/java/org/wildfly/security/http/external/ExternalAuthenticationMechanism.java b/http/external/src/main/java/org/wildfly/security/http/external/ExternalAuthenticationMechanism.java
index 8dc6da9b21a..bd6021da8cc 100644
--- a/http/external/src/main/java/org/wildfly/security/http/external/ExternalAuthenticationMechanism.java
+++ b/http/external/src/main/java/org/wildfly/security/http/external/ExternalAuthenticationMechanism.java
@@ -59,19 +59,24 @@ public void evaluateRequest(HttpServerRequest request) throws HttpAuthentication
String remoteUser = request.getRemoteUser();
if (remoteUser == null) {
+ httpExternal.trace("The remote-user was not obtained from the request");
request.noAuthenticationInProgress();
return;
}
if (authorize(remoteUser)) {
+ httpExternal.tracef("Authorization of user [%s] succeed", remoteUser);
succeed(request);
} else {
+ httpExternal.tracef("Authorization of user [%s] failed", remoteUser);
fail(request);
}
}
private boolean authorize(String username) throws HttpAuthenticationException {
+ httpExternal.tracef("Authorizing username: [%s]",username);
+
AuthorizeCallback authorizeCallback = new AuthorizeCallback(username, username);
try {
MechanismUtil.handleCallbacks(httpExternal, callbackHandler, authorizeCallback);
@@ -88,6 +93,7 @@ private void succeed(HttpServerRequest request) throws HttpAuthenticationExcepti
MechanismUtil.handleCallbacks(httpExternal, callbackHandler, AuthenticationCompleteCallback.SUCCEEDED);
request.authenticationComplete();
} catch (AuthenticationMechanismException e) {
+ httpExternal.trace("Failed to complete successful authentication", e);
throw e.toHttpAuthenticationException();
} catch (UnsupportedCallbackException e) {
throw httpExternal.mechCallbackHandlerFailedForUnknownReason(e).toHttpAuthenticationException();
@@ -99,6 +105,7 @@ private void fail(HttpServerRequest request) throws HttpAuthenticationException
MechanismUtil.handleCallbacks(httpExternal, callbackHandler, AuthenticationCompleteCallback.FAILED);
request.authenticationFailed(httpExternal.authenticationFailed(), response -> response.setStatusCode(FORBIDDEN));
} catch (AuthenticationMechanismException e) {
+ httpExternal.trace("Failed authentication not completed", e);
throw e.toHttpAuthenticationException();
} catch (UnsupportedCallbackException e) {
throw httpExternal.mechCallbackHandlerFailedForUnknownReason(e).toHttpAuthenticationException();
diff --git a/http/form/pom.xml b/http/form/pom.xml
index bc27ee4898d..2b72dc32603 100644
--- a/http/form/pom.xml
+++ b/http/form/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/http/oidc/pom.xml b/http/oidc/pom.xml
index fd1e3bba60b..f0a272321fb 100644
--- a/http/oidc/pom.xml
+++ b/http/oidc/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
@@ -128,6 +128,11 @@
keycloak-admin-client
test
+
+ org.keycloak
+ keycloak-services
+ test
+
org.jboss.logmanager
jboss-logmanager
@@ -164,7 +169,7 @@
test
- org.glassfish
+ org.eclipse.parsson
jakarta.json
test
@@ -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 773b59d8bb3..e836cc3b468 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;
@@ -88,11 +88,11 @@ interface ElytronMessages extends BasicLogger {
@Message(id = 23011, value = "Refresh token failure")
void refreshTokenFailure(@Cause Throwable cause);
- @LogMessage(level = ERROR)
+ @LogMessage(level = DEBUG)
@Message(id = 23012, value = "Refresh token failure status: %d %s")
void refreshTokenFailureStatus(int status, String error);
- @LogMessage(level = ERROR)
+ @LogMessage(level = DEBUG)
@Message(id = 23013, value = "Failed verification of token: %s")
void failedVerificationOfToken(String error);
@@ -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/IDToken.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/IDToken.java
index b6445cc412e..d40be6bfce8 100644
--- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/IDToken.java
+++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/IDToken.java
@@ -20,10 +20,12 @@
import static org.wildfly.security.http.oidc.ElytronMessages.log;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.json.JsonObject;
import jakarta.json.JsonValue;
-import java.util.Map;
+import java.util.HashMap;
import org.jose4j.jwt.JwtClaims;
@@ -163,7 +165,13 @@ public AddressClaimSet getAddress() {
if (! (addressValueAsJson instanceof JsonObject)) {
throw log.invalidTokenClaimValue();
}
- return new AddressClaimSet((Map) addressValueAsJson);
+ HashMap result;
+ try {
+ result = new ObjectMapper().readValue(addressValueAsJson.toString(), HashMap.class);
+ } catch (JsonProcessingException e) {
+ throw log.invalidTokenClaimValue();
+ }
+ return new AddressClaimSet(result);
}
/**
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 00000000000..819e5950671
--- /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 4da8d3a5384..13df213373b 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 00000000000..03546d8a23f
--- /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 f42313b7f58..c6b38c9ef4d 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,13 @@
public class Oidc {
public static final String ACCEPT = "Accept";
+ public static final String ADAPTER_STATE_COOKIE_PATH = "adapter-state-cookie-path";
+ public static final String ALLOW_ANY_HOSTNAME = "allow-any-hostname";
+ public static final String ALWAYS_REFRESH_TOKEN = "always-refresh-token";
+ public static final String AUTH_SERVER_URL = "auth-server-url";
+ public static final String AUTHENTICATION_REQUEST_FORMAT = "authentication-request-format";
+ public static final String AUTODETECT_BEARER_ONLY = "autodetect-bearer-only";
+ public static final String BEARER_ONLY = "bearer-only";
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";
@@ -54,14 +61,27 @@ public class Oidc {
public static final String KEYCLOAK_REALMS_PATH = "realms/";
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 CORS_MAX_AGE = "cors-max-age";
+ public static final String CORS_ALLOWED_HEADERS = "cors-allowed-headers";
+ public static final String CORS_ALLOWED_METHODS = "cors-allowed-methods";
+ public static final String CORS_EXPOSED_HEADERS = "cors-exposed-headers";
+ public static final String CONNECTION_POOL_SIZE = "connection-pool-size";
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 CREDENTIALS = "credentials";
+ public static final String DISABLE_TRUST_MANAGER = "disable-trust-manager";
public static final String SLASH = "/";
public static final String OIDC_CLIENT_CONTEXT_KEY = OidcClientContext.class.getName();
public static final String CLIENT_ID = "client_id";
+ public static final String CLIENT_ID_JSON_VALUE = "client-id";
+ public static final String CLIENT_KEYSTORE = "client-keystore";
+ public static final String CLIENT_KEYSTORE_PASSWORD = "client-keystore-password";
+ public static final String CLIENT_KEY_PASSWORD = "client-key-password";
public static final String CODE = "code";
+ public static final String ENABLE_CORS = "enable-cors";
public static final String ERROR = "error";
public static final String ERROR_DESCRIPTION = "error_description";
+ public static final String EXPOSE_TOKEN = "expose-token";
public static final String FACES_REQUEST = "Faces-Request";
public static final String GRANT_TYPE = "grant_type";
public static final String INVALID_TOKEN = "invalid_token";
@@ -73,7 +93,17 @@ public class Oidc {
public static final String OPTIONS = "OPTIONS";
public static final String PARTIAL = "partial/";
public static final String PASSWORD = "password";
+ public static final String PRINCIPAL_ATTRIBUTE = "principal-attribute";
public static final String PROMPT = "prompt";
+ public static final String PROXY_URL = "proxy-url";
+ public static final String PUBLIC_CLIENT = "public-client";
+ public static final String REALM = "realm";
+ public static final String REALM_PUBLIC_KEY = "realm-public-key";
+ public static final String REGISTER_NODE_AT_STARTUP = "register-node-at-startup";
+ public static final String REGISTER_NODE_PERIOD = "register-node-period";
+ public static final String REQUEST = "request";
+ public static final String REQUEST_URI = "request_uri";
+ public static final String RESOURCE = "resource";
public static final String SCOPE = "scope";
public static final String UI_LOCALES = "ui_locales";
public static final String USERNAME = "username";
@@ -83,6 +113,7 @@ public class Oidc {
public static final String RESPONSE_TYPE = "response_type";
public static final String SESSION_STATE = "session_state";
public static final String SOAP_ACTION = "SOAPAction";
+ public static final String SSL_REQUIRED = "ssl-required";
public static final String STALE_TOKEN = "Stale token";
public static final String STATE = "state";
public static final int INVALID_ISSUED_FOR_CLAIM = -1;
@@ -115,8 +146,33 @@ public class Oidc {
static final String DEFAULT_TOKEN_SIGNATURE_ALGORITHM = "RS256";
public static final String DISABLE_TYP_CLAIM_VALIDATION_PROPERTY_NAME = "wildfly.elytron.oidc.disable.typ.claim.validation";
public static final String ALLOW_QUERY_PARAMS_PROPERTY_NAME = "wildfly.elytron.oidc.allow.query.params";
+ public static final String TOKEN_MINIMUM_TIME_TO_LIVE = "token-minimum-time-to-live";
+ public static final String TOKEN_SIGNATURE_ALGORITHM = "token-signature-algorithm";
+ public static final String TOKEN_STORE = "token-store";
+ public static final String TRUSTSTORE = "truststore";
+ public static final String TRUSTSTORE_PASSWORD = "truststore-password";
+ public static final String TURN_OFF_CHANGE_SESSION_ID_ON_LOGIN = "turn-off-change-session-id-on-login";
+ public static final String USE_RESOURCE_ROLE_MAPPINGS = "use-resource-role-mappings";
+ public static final String USE_REALM_ROLE_MAPPINGS = "use-realm-role-mappings";
public static final String X_REQUESTED_WITH = "X-Requested-With";
public static final String XML_HTTP_REQUEST = "XMLHttpRequest";
+ public static final String MIN_TIME_BETWEEN_JWKS_REQUESTS = "min-time-between-jwks-requests";
+ public static final String PUBLIC_KEY_CACHE_TTL = "public-key-cache-ttl";
+ public static final String IGNORE_OAUTH_QUERY_PARAMETER = "ignore-oauth-query-parameter";
+ public static final String VERIFY_TOKEN_AUDIENCE = "verify-token-audience";
+ public static final String REQUEST_OBJECT_SIGNING_ALGORITHM = "request-object-signing-algorithm";
+ public static final String REQUEST_OBJECT_ENCRYPTION_ALG_VALUE = "request-object-encryption-alg-value";
+ public static final String REQUEST_OBJECT_ENCRYPTION_ENC_VALUE = "request-object-encryption-enc-value";
+ public static final String REQUEST_OBJECT_SIGNING_KEYSTORE_FILE = "request-object-signing-keystore-file";
+ public static final String REQUEST_OBJECT_SIGNING_KEYSTORE_PASSWORD = "request-object-signing-keystore-password";
+ public static final String REQUEST_OBJECT_SIGNING_KEY_PASSWORD = "request-object-signing-key-password";
+ public static final String REQUEST_OBJECT_SIGNING_KEY_ALIAS = "request-object-signing-key-alias";
+ public static final String REQUEST_OBJECT_SIGNING_KEYSTORE_TYPE = "request-object-signing-keystore-type";
+ public static final String REDIRECT_REWRITE_RULES = "redirect-rewrite-rules";
+ public static final String ENABLE_PKCE = "enable-pkce";
+ public static final String CONFIDENTIAL_PORT = "confidential-port";
+ public static final String ENABLE_BASIC_AUTH = "enable-basic-auth";
+ public static final String PROVIDER_URL = "provider-url";
/**
* Bearer token pattern.
@@ -201,6 +257,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 db872b30a89..ca56da28633 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;
}
@@ -419,10 +463,7 @@ public void setSSLRequired(SSLRequired sslRequired) {
}
public boolean isSSLEnabled() {
- if (SSLRequired.NONE == sslRequired) {
- return false;
- }
- return true;
+ return SSLRequired.NONE != sslRequired;
}
public int getConfidentialPort() {
@@ -651,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 99f9b185a5d..43bebace9f6 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;
@@ -100,6 +104,44 @@ protected OidcClientConfiguration internalBuild(final OidcJsonConfiguration oidc
if (oidcJsonConfiguration.getTokenCookiePath() != null) {
oidcClientConfiguration.setOidcStateCookiePath(oidcJsonConfiguration.getTokenCookiePath());
}
+ 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());
@@ -190,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 3c249bb846b..f5d930bd525 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/OidcHttpFacade.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcHttpFacade.java
index d27a3a9f200..ba5cb0fa3a9 100644
--- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcHttpFacade.java
+++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcHttpFacade.java
@@ -29,7 +29,6 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
@@ -121,8 +120,7 @@ public Principal getPrincipal() {
SecurityIdentityCallback securityIdentityCallback = new SecurityIdentityCallback();
IdentityCredentialCallback credentialCallback = new IdentityCredentialCallback(new BearerTokenCredential(OidcPrincipal.class.cast(principal).getOidcSecurityContext().getTokenString()), true);
callbackHandler.handle(new Callback[]{credentialCallback, AuthenticationCompleteCallback.SUCCEEDED, securityIdentityCallback});
- SecurityIdentity securityIdentity = securityIdentityCallback.getSecurityIdentity();
- return securityIdentity;
+ return securityIdentityCallback.getSecurityIdentity();
}
} catch (UnsupportedCallbackException | IOException e) {
throw new RuntimeException(e);
@@ -205,11 +203,7 @@ public String getMethod() {
@Override
public String getURI() {
- try {
- return URLDecoder.decode(request.getRequestURI().toString(), "UTF-8");
- } catch (UnsupportedEncodingException e) {
- throw log.failedToDecodeRequestUri(e);
- }
+ return request.getRequestURI().toString();
}
@Override
@@ -230,7 +224,7 @@ public String getFirstParam(String param) {
@Override
public String getQueryParamValue(String param) {
URI requestURI = request.getRequestURI();
- String query = requestURI.getQuery();
+ String query = requestURI.getRawQuery();
if (query != null) {
String[] parameters = query.split("&");
for (String parameter : parameters) {
@@ -388,47 +382,7 @@ public void setCookie(final String name, final String value, final String path,
}
private void setCookie(final String name, final String value, final String path, final String domain, final int maxAge, final boolean secure, final boolean httpOnly, HttpServerResponse response) {
- response.setResponseCookie(new HttpServerCookie() {
- @Override
- public String getName() {
- return name;
- }
-
- @Override
- public String getValue() {
- return value;
- }
-
- @Override
- public String getDomain() {
- return domain;
- }
-
- @Override
- public int getMaxAge() {
- return maxAge;
- }
-
- @Override
- public String getPath() {
- return path;
- }
-
- @Override
- public boolean isSecure() {
- return secure;
- }
-
- @Override
- public int getVersion() {
- return 0;
- }
-
- @Override
- public boolean isHttpOnly() {
- return httpOnly;
- }
- });
+ response.setResponseCookie(HttpServerCookie.getInstance(name, value, domain, maxAge, path, secure, 0, httpOnly));
}
@Override
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..4f107b79591 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
@@ -19,6 +19,61 @@
package org.wildfly.security.http.oidc;
import static org.wildfly.security.http.oidc.Oidc.DEFAULT_TOKEN_SIGNATURE_ALGORITHM;
+import static org.wildfly.security.http.oidc.Oidc.ADAPTER_STATE_COOKIE_PATH;
+import static org.wildfly.security.http.oidc.Oidc.ALLOW_ANY_HOSTNAME;
+import static org.wildfly.security.http.oidc.Oidc.ALWAYS_REFRESH_TOKEN;
+import static org.wildfly.security.http.oidc.Oidc.AUTH_SERVER_URL;
+import static org.wildfly.security.http.oidc.Oidc.AUTHENTICATION_REQUEST_FORMAT;
+import static org.wildfly.security.http.oidc.Oidc.AUTODETECT_BEARER_ONLY;
+import static org.wildfly.security.http.oidc.Oidc.BEARER_ONLY;
+import static org.wildfly.security.http.oidc.Oidc.CLIENT_ID_JSON_VALUE;
+import static org.wildfly.security.http.oidc.Oidc.CLIENT_KEYSTORE;
+import static org.wildfly.security.http.oidc.Oidc.CLIENT_KEYSTORE_PASSWORD;
+import static org.wildfly.security.http.oidc.Oidc.CLIENT_KEY_PASSWORD;
+import static org.wildfly.security.http.oidc.Oidc.CONFIDENTIAL_PORT;
+import static org.wildfly.security.http.oidc.Oidc.CONNECTION_POOL_SIZE;
+import static org.wildfly.security.http.oidc.Oidc.CORS_ALLOWED_HEADERS;
+import static org.wildfly.security.http.oidc.Oidc.CORS_ALLOWED_METHODS;
+import static org.wildfly.security.http.oidc.Oidc.CORS_EXPOSED_HEADERS;
+import static org.wildfly.security.http.oidc.Oidc.CORS_MAX_AGE;
+import static org.wildfly.security.http.oidc.Oidc.CREDENTIALS;
+import static org.wildfly.security.http.oidc.Oidc.DISABLE_TRUST_MANAGER;
+import static org.wildfly.security.http.oidc.Oidc.ENABLE_BASIC_AUTH;
+import static org.wildfly.security.http.oidc.Oidc.ENABLE_CORS;
+import static org.wildfly.security.http.oidc.Oidc.ENABLE_PKCE;
+import static org.wildfly.security.http.oidc.Oidc.EXPOSE_TOKEN;
+import static org.wildfly.security.http.oidc.Oidc.IGNORE_OAUTH_QUERY_PARAMETER;
+import static org.wildfly.security.http.oidc.Oidc.MIN_TIME_BETWEEN_JWKS_REQUESTS;
+import static org.wildfly.security.http.oidc.Oidc.PRINCIPAL_ATTRIBUTE;
+import static org.wildfly.security.http.oidc.Oidc.PROVIDER_URL;
+import static org.wildfly.security.http.oidc.Oidc.PROXY_URL;
+import static org.wildfly.security.http.oidc.Oidc.PUBLIC_CLIENT;
+import static org.wildfly.security.http.oidc.Oidc.PUBLIC_KEY_CACHE_TTL;
+import static org.wildfly.security.http.oidc.Oidc.REDIRECT_REWRITE_RULES;
+import static org.wildfly.security.http.oidc.Oidc.REGISTER_NODE_AT_STARTUP;
+import static org.wildfly.security.http.oidc.Oidc.REGISTER_NODE_PERIOD;
+import static org.wildfly.security.http.oidc.Oidc.REALM;
+import static org.wildfly.security.http.oidc.Oidc.REALM_PUBLIC_KEY;
+import static org.wildfly.security.http.oidc.Oidc.RESOURCE;
+import static org.wildfly.security.http.oidc.Oidc.REQUEST_OBJECT_ENCRYPTION_ALG_VALUE;
+import static org.wildfly.security.http.oidc.Oidc.REQUEST_OBJECT_ENCRYPTION_ENC_VALUE;
+import static org.wildfly.security.http.oidc.Oidc.REQUEST_OBJECT_SIGNING_ALGORITHM;
+import static org.wildfly.security.http.oidc.Oidc.REQUEST_OBJECT_SIGNING_KEY_ALIAS;
+import static org.wildfly.security.http.oidc.Oidc.REQUEST_OBJECT_SIGNING_KEY_PASSWORD;
+import static org.wildfly.security.http.oidc.Oidc.REQUEST_OBJECT_SIGNING_KEYSTORE_FILE;
+import static org.wildfly.security.http.oidc.Oidc.REQUEST_OBJECT_SIGNING_KEYSTORE_PASSWORD;
+import static org.wildfly.security.http.oidc.Oidc.REQUEST_OBJECT_SIGNING_KEYSTORE_TYPE;
+import static org.wildfly.security.http.oidc.Oidc.SCOPE;
+import static org.wildfly.security.http.oidc.Oidc.SSL_REQUIRED;
+import static org.wildfly.security.http.oidc.Oidc.TOKEN_MINIMUM_TIME_TO_LIVE;
+import static org.wildfly.security.http.oidc.Oidc.TOKEN_SIGNATURE_ALGORITHM;
+import static org.wildfly.security.http.oidc.Oidc.TOKEN_STORE;
+import static org.wildfly.security.http.oidc.Oidc.TRUSTSTORE;
+import static org.wildfly.security.http.oidc.Oidc.TRUSTSTORE_PASSWORD;
+import static org.wildfly.security.http.oidc.Oidc.TURN_OFF_CHANGE_SESSION_ID_ON_LOGIN;
+import static org.wildfly.security.http.oidc.Oidc.USE_RESOURCE_ROLE_MAPPINGS;
+import static org.wildfly.security.http.oidc.Oidc.USE_REALM_ROLE_MAPPINGS;
+import static org.wildfly.security.http.oidc.Oidc.VERIFY_TOKEN_AUDIENCE;
import java.util.Map;
import java.util.TreeMap;
@@ -34,116 +89,143 @@
* @author John D. Ament
* @author Farah Juma
*/
-@JsonPropertyOrder({"realm", "realm-public-key", "auth-server-url", "ssl-required",
- "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",
- "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"
+@JsonPropertyOrder({REALM, REALM_PUBLIC_KEY, AUTH_SERVER_URL, SSL_REQUIRED,
+ 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,
+ 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,
+ 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 {
- @JsonProperty("allow-any-hostname")
+ @JsonProperty(ALLOW_ANY_HOSTNAME)
protected boolean allowAnyHostname;
- @JsonProperty("disable-trust-manager")
+ @JsonProperty(DISABLE_TRUST_MANAGER)
protected boolean disableTrustManager;
- @JsonProperty("truststore")
+ @JsonProperty(TRUSTSTORE)
protected String truststore;
- @JsonProperty("truststore-password")
+ @JsonProperty(TRUSTSTORE_PASSWORD)
protected String truststorePassword;
- @JsonProperty("client-keystore")
+ @JsonProperty(CLIENT_KEYSTORE)
protected String clientKeystore;
- @JsonProperty("client-keystore-password")
+ @JsonProperty(CLIENT_KEYSTORE_PASSWORD)
protected String clientKeystorePassword;
- @JsonProperty("client-key-password")
+ @JsonProperty(CLIENT_KEY_PASSWORD)
protected String clientKeyPassword;
- @JsonProperty("connection-pool-size")
+ @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")
+ @JsonProperty(ALWAYS_REFRESH_TOKEN)
protected boolean alwaysRefreshToken = false;
- @JsonProperty("register-node-at-startup")
+ @JsonProperty(REGISTER_NODE_AT_STARTUP)
protected boolean registerNodeAtStartup = false;
- @JsonProperty("register-node-period")
+ @JsonProperty(REGISTER_NODE_PERIOD)
protected int registerNodePeriod = -1;
- @JsonProperty("token-store")
+ @JsonProperty(TOKEN_STORE)
protected String tokenStore;
- @JsonProperty("adapter-state-cookie-path")
+ @JsonProperty(ADAPTER_STATE_COOKIE_PATH)
protected String tokenCookiePath;
- @JsonProperty("principal-attribute")
+ @JsonProperty(PRINCIPAL_ATTRIBUTE)
protected String principalAttribute;
- @JsonProperty("turn-off-change-session-id-on-login")
+ @JsonProperty(TURN_OFF_CHANGE_SESSION_ID_ON_LOGIN)
protected Boolean turnOffChangeSessionIdOnLogin;
- @JsonProperty("token-minimum-time-to-live")
+ @JsonProperty(TOKEN_MINIMUM_TIME_TO_LIVE)
protected int tokenMinimumTimeToLive = 0;
- @JsonProperty("min-time-between-jwks-requests")
+ @JsonProperty(MIN_TIME_BETWEEN_JWKS_REQUESTS)
protected int minTimeBetweenJwksRequests = 10;
- @JsonProperty("public-key-cache-ttl")
+ @JsonProperty(PUBLIC_KEY_CACHE_TTL)
protected int publicKeyCacheTtl = 86400; // 1 day
// https://tools.ietf.org/html/rfc7636
- @JsonProperty("enable-pkce")
+ @JsonProperty(ENABLE_PKCE)
protected boolean pkce = false;
- @JsonProperty("ignore-oauth-query-parameter")
+ @JsonProperty(IGNORE_OAUTH_QUERY_PARAMETER)
protected boolean ignoreOAuthQueryParameter = false;
- @JsonProperty("verify-token-audience")
+ @JsonProperty(VERIFY_TOKEN_AUDIENCE)
protected boolean verifyTokenAudience = false;
- @JsonProperty("confidential-port")
+ @JsonProperty(CONFIDENTIAL_PORT)
protected int confidentialPort;
- @JsonProperty("resource")
+ @JsonProperty(RESOURCE)
protected String resource;
- @JsonProperty("use-resource-role-mappings")
+ @JsonProperty(USE_RESOURCE_ROLE_MAPPINGS)
protected boolean useResourceRoleMappings;
- @JsonProperty("use-realm-role-mappings")
+ @JsonProperty(USE_REALM_ROLE_MAPPINGS)
protected boolean useRealmRoleMappings = true;
- @JsonProperty("enable-cors")
+ @JsonProperty(ENABLE_CORS)
protected boolean cors;
- @JsonProperty("cors-max-age")
+ @JsonProperty(CORS_MAX_AGE)
protected int corsMaxAge = -1;
- @JsonProperty("cors-allowed-headers")
+ @JsonProperty(CORS_ALLOWED_HEADERS)
protected String corsAllowedHeaders;
- @JsonProperty("cors-allowed-methods")
+ @JsonProperty(CORS_ALLOWED_METHODS)
protected String corsAllowedMethods;
- @JsonProperty("cors-exposed-headers")
+ @JsonProperty(CORS_EXPOSED_HEADERS)
protected String corsExposedHeaders;
- @JsonProperty("expose-token")
+ @JsonProperty(EXPOSE_TOKEN)
protected boolean exposeToken;
- @JsonProperty("bearer-only")
+ @JsonProperty(BEARER_ONLY)
protected boolean bearerOnly;
- @JsonProperty("autodetect-bearer-only")
+ @JsonProperty(AUTODETECT_BEARER_ONLY)
protected boolean autodetectBearerOnly;
- @JsonProperty("enable-basic-auth")
+ @JsonProperty(ENABLE_BASIC_AUTH)
protected boolean enableBasicAuth;
- @JsonProperty("public-client")
+ @JsonProperty(PUBLIC_CLIENT)
protected boolean publicClient;
- @JsonProperty("credentials")
+ @JsonProperty(CREDENTIALS)
protected Map credentials = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
- @JsonProperty("redirect-rewrite-rules")
+ @JsonProperty(REDIRECT_REWRITE_RULES)
protected Map redirectRewriteRules;
- @JsonProperty("realm")
+ @JsonProperty(REALM)
protected String realm;
- @JsonProperty("realm-public-key")
+ @JsonProperty(REALM_PUBLIC_KEY)
protected String realmKey;
- @JsonProperty("auth-server-url")
+ @JsonProperty(AUTH_SERVER_URL)
protected String authServerUrl;
- @JsonProperty("ssl-required")
+ @JsonProperty(SSL_REQUIRED)
protected String sslRequired;
- @JsonProperty("provider-url")
+ @JsonProperty(PROVIDER_URL)
protected String providerUrl;
- @JsonProperty("client-id")
+ @JsonProperty(CLIENT_ID_JSON_VALUE)
protected String clientId;
- @JsonProperty("token-signature-algorithm")
+ @JsonProperty(TOKEN_SIGNATURE_ALGORITHM)
protected String tokenSignatureAlgorithm = DEFAULT_TOKEN_SIGNATURE_ALGORITHM;
+ @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}.
*/
- @JsonProperty("proxy-url")
+ @JsonProperty(PROXY_URL)
protected String proxyUrl;
public boolean isAllowAnyHostname() {
@@ -178,6 +260,13 @@ public void setTruststorePassword(String truststorePassword) {
this.truststorePassword = truststorePassword;
}
+ public String getRequestObjectSigningKeyStoreFile() {
+ return requestObjectSigningKeyStoreFile;
+ }
+
+ public void setRequestObjectSigningKeyStoreFile(String requestObjectSigningKeyStoreFile) {
+ this.requestObjectSigningKeyStoreFile = requestObjectSigningKeyStoreFile;
+ }
public String getClientKeystore() {
return clientKeystore;
}
@@ -186,6 +275,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;
}
@@ -198,10 +303,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;
}
@@ -511,5 +632,43 @@ public void setTokenSignatureAlgorithm(String tokenSignatureAlgorithm) {
this.tokenSignatureAlgorithm = tokenSignatureAlgorithm;
}
+ public String getScope() {
+ return scope;
+ }
+
+ 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/OidcPrincipal.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcPrincipal.java
index dc26a5c49be..b506b6b7596 100644
--- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcPrincipal.java
+++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcPrincipal.java
@@ -50,9 +50,7 @@ public boolean equals(Object o) {
OidcPrincipal that = (OidcPrincipal) o;
- if (! name.equals(that.name)) return false;
-
- return true;
+ return name.equals(that.name);
}
@Override
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..7619a89b4b0 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() {
@@ -320,8 +329,8 @@ public void setClaimTypesSupported(List claimTypesSupported) {
this.claimTypesSupported = claimTypesSupported;
}
- public Boolean getClaimsParameterSupported() {
- return claimsParameterSupported;
+ public boolean getClaimsParameterSupported() {
+ return claimsParameterSupported == null ? false : claimsParameterSupported;
}
public void setClaimsParameterSupported(Boolean claimsParameterSupported) {
@@ -336,16 +345,16 @@ public void setScopesSupported(List scopesSupported) {
this.scopesSupported = scopesSupported;
}
- public Boolean getRequestParameterSupported() {
- return requestParameterSupported;
+ public boolean getRequestParameterSupported() {
+ return requestParameterSupported == null ? false : requestParameterSupported;
}
public void setRequestParameterSupported(Boolean requestParameterSupported) {
this.requestParameterSupported = requestParameterSupported;
}
- public Boolean getRequestUriParameterSupported() {
- return requestUriParameterSupported;
+ public boolean getRequestUriParameterSupported() {
+ return requestUriParameterSupported == null ? false : requestUriParameterSupported;
}
public void setRequestUriParameterSupported(Boolean requestUriParameterSupported) {
@@ -384,12 +393,12 @@ public void setRevocationEndpointAuthSigningAlgValuesSupported(List revo
this.revocationEndpointAuthSigningAlgValuesSupported = revocationEndpointAuthSigningAlgValuesSupported;
}
- public Boolean getBackchannelLogoutSupported() {
- return backchannelLogoutSupported;
+ public boolean getBackchannelLogoutSupported() {
+ return backchannelLogoutSupported == null ? false : backchannelLogoutSupported;
}
- public Boolean getBackchannelLogoutSessionSupported() {
- return backchannelLogoutSessionSupported;
+ public boolean getBackchannelLogoutSessionSupported() {
+ return backchannelLogoutSessionSupported == null ? false : backchannelLogoutSessionSupported;
}
public void setBackchannelLogoutSessionSupported(Boolean backchannelLogoutSessionSupported) {
@@ -407,8 +416,32 @@ public List getCodeChallengeMethodsSupported() {
// KEYCLOAK-6771 Certificate Bound Token
// https://tools.ietf.org/html/draft-ietf-oauth-mtls-08#section-6.2
- public Boolean getTlsClientCertificateBoundAccessTokens() {
- return tlsClientCertificateBoundAccessTokens;
+ public boolean getTlsClientCertificateBoundAccessTokens() {
+ return tlsClientCertificateBoundAccessTokens == null ? false : 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
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 bdcc7168e8f..5ef5c26122e 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,15 +55,27 @@
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;
import java.util.List;
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;
/**
@@ -181,10 +201,13 @@ protected String getRedirectUri(String state) {
List forwardableQueryParams = Arrays.asList(LOGIN_HINT, DOMAIN_HINT, KC_IDP_HINT, PROMPT, MAX_AGE, UI_LOCALES, SCOPE);
List forwardedQueryParams = new ArrayList<>(forwardableQueryParams.size());
+ Set allScopes = new HashSet<>();
+ addScopes(deployment.getScope(), allScopes);
+
for (String paramName : forwardableQueryParams) {
String paramValue = getQueryParamValue(facade, paramName);
if (SCOPE.equals(paramName)) {
- paramValue = addOidcScopeIfNeeded(paramValue);
+ paramValue = combineAndReorderScopes(allScopes, paramValue);
}
if (paramValue != null && !paramValue.isEmpty()) {
forwardedQueryParams.add(new BasicNameValuePair(paramName, paramValue));
@@ -195,18 +218,74 @@ protected String getRedirectUri(String state) {
if (deployment.getAuthUrl() == null) {
return null;
}
- URIBuilder redirectUriBuilder = new URIBuilder(deployment.getAuthUrl())
- .addParameter(RESPONSE_TYPE, CODE)
- .addParameter(CLIENT_ID, deployment.getResourceName())
- .addParameter(REDIRECT_URI, rewrittenRedirectUri(url))
- .addParameter(STATE, state);
- redirectUriBuilder.addParameters(forwardedQueryParams);
+
+ String redirectUri = rewrittenRedirectUri(url);
+ URIBuilder redirectUriBuilder = new URIBuilder(deployment.getAuthUrl());
+ redirectUriBuilder.addParameter(RESPONSE_TYPE, CODE)
+ .addParameter(CLIENT_ID, deployment.getResourceName());
+
+ switch (deployment.getAuthenticationRequestFormat()) {
+ case REQUEST:
+ if (deployment.getRequestParameterSupported()) {
+ // add request objects into request parameter
+ 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;
}
@@ -435,4 +514,112 @@ private static boolean hasScope(String scopeParam, String targetScope) {
}
return false;
}
+
+ private String combineAndReorderScopes(Set allScopes, String paramValue) {
+ StringBuilder combinedScopes = new StringBuilder();
+ addScopes(paramValue, allScopes);
+
+ //some OpenID providers require openid scope to be added in the beginning
+ combinedScopes.append(OIDC_SCOPE);
+ for (String scope : allScopes) {
+ if (!scope.equals(OIDC_SCOPE)) {
+ combinedScopes.append(" ").append(scope);
+ }
+ }
+ return combinedScopes.toString();
+ }
+
+ private void addScopes(String scopes, Set allScopes) {
+ if (scopes != null && !scopes.isEmpty()) {
+ 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/OidcSecurityContext.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcSecurityContext.java
index 5556f311967..c539a2e6224 100644
--- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcSecurityContext.java
+++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/OidcSecurityContext.java
@@ -76,8 +76,8 @@ public String getRealm() {
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
try {
- token = new AccessToken(new JwtConsumerBuilder().setSkipSignatureVerification().setSkipAllValidators().build().processToClaims(tokenString));
- idToken = new IDToken(new JwtConsumerBuilder().setSkipSignatureVerification().setSkipAllValidators().build().processToClaims(idTokenString));
+ token = tokenString == null ? null : new AccessToken(new JwtConsumerBuilder().setSkipSignatureVerification().setSkipAllValidators().build().processToClaims(tokenString));
+ idToken = idTokenString == null ? null : new IDToken(new JwtConsumerBuilder().setSkipSignatureVerification().setSkipAllValidators().build().processToClaims(idTokenString));
} catch (InvalidJwtException e) {
throw log.unableToParseToken();
}
diff --git a/http/oidc/src/main/java/org/wildfly/security/http/oidc/RequestAuthenticator.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/RequestAuthenticator.java
index ca894423206..87b18e0abef 100644
--- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/RequestAuthenticator.java
+++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/RequestAuthenticator.java
@@ -54,10 +54,8 @@ public RequestAuthenticator(OidcHttpFacade facade, OidcClientConfiguration deplo
public AuthOutcome authenticate() {
AuthOutcome authenticate = doAuthenticate();
- if (AuthOutcome.AUTHENTICATED.equals(authenticate)) {
- if (! facade.isAuthorized()) {
- return AuthOutcome.FAILED;
- }
+ if (AuthOutcome.AUTHENTICATED.equals(authenticate) && !facade.isAuthorized()) {
+ return AuthOutcome.FAILED;
}
return authenticate;
}
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 ad50d715c56..3a203541ee4 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/main/java/org/wildfly/security/http/oidc/TokenValidator.java b/http/oidc/src/main/java/org/wildfly/security/http/oidc/TokenValidator.java
index b1540bb1146..746318043f6 100644
--- a/http/oidc/src/main/java/org/wildfly/security/http/oidc/TokenValidator.java
+++ b/http/oidc/src/main/java/org/wildfly/security/http/oidc/TokenValidator.java
@@ -274,6 +274,18 @@ public ErrorCodeValidator.Error validate(JwtContext jwtContext) throws Malformed
}
return null;
}
+
+ private static String getAccessTokenHash(String accessTokenString, String jwsAlgorithm) throws NoSuchAlgorithmException {
+ byte[] inputBytes = accessTokenString.getBytes(StandardCharsets.UTF_8);
+ String javaAlgName = getJavaAlgorithmForHash(jwsAlgorithm);
+ MessageDigest md = MessageDigest.getInstance(javaAlgName);
+ md.update(inputBytes);
+ byte[] hash = md.digest();
+ int hashLength = hash.length / 2;
+ byte[] hashInput = Arrays.copyOf(hash, hashLength); // leftmost half of the hash
+ return ByteIterator.ofBytes(hashInput).base64Encode(BASE64_URL, false).drainToString();
+ }
+
}
private static class TypeValidator implements ErrorCodeValidator {
@@ -297,17 +309,6 @@ public ErrorCodeValidator.Error validate(JwtContext jwtContext) throws Malformed
}
}
- private static String getAccessTokenHash(String accessTokenString, String jwsAlgorithm) throws NoSuchAlgorithmException {
- byte[] inputBytes = accessTokenString.getBytes(StandardCharsets.UTF_8);
- String javaAlgName = getJavaAlgorithmForHash(jwsAlgorithm);
- MessageDigest md = MessageDigest.getInstance(javaAlgName);
- md.update(inputBytes);
- byte[] hash = md.digest();
- int hashLength = hash.length / 2;
- byte[] hashInput = Arrays.copyOf(hash, hashLength); // leftmost half of the hash
- return ByteIterator.ofBytes(hashInput).base64Encode(BASE64_URL, false).drainToString();
- }
-
public static class VerifiedTokens {
private final AccessToken accessToken;
diff --git a/http/oidc/src/test/java/org/wildfly/security/http/oidc/BearerTest.java b/http/oidc/src/test/java/org/wildfly/security/http/oidc/BearerTest.java
index 18c4b2f087d..275c9b181ac 100644
--- a/http/oidc/src/test/java/org/wildfly/security/http/oidc/BearerTest.java
+++ b/http/oidc/src/test/java/org/wildfly/security/http/oidc/BearerTest.java
@@ -27,7 +27,10 @@
import static org.wildfly.security.http.oidc.Oidc.OIDC_NAME;
import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
@@ -36,6 +39,7 @@
import java.util.Map;
import org.apache.http.HttpStatus;
+import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -146,6 +150,35 @@ public static void generalCleanup() throws Exception {
}
}
+ @Test
+ public void testOIDCSecurityContextDeserialization() throws Exception {
+ String accessTokenString = KeycloakConfiguration.getAccessToken(KEYCLOAK_CONTAINER.getAuthServerUrl(), TEST_REALM, KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD, CLIENT_ID, CLIENT_SECRET);
+ AccessToken accessToken = new AccessToken(new JwtConsumerBuilder().setSkipSignatureVerification().setSkipAllValidators().build().processToClaims(accessTokenString));
+ OidcSecurityContext oidcSecurityContext = new OidcSecurityContext(accessTokenString, accessToken, null, null);
+ OidcPrincipal oidcPrincipal = new OidcPrincipal("alice", oidcSecurityContext);
+
+ // Serialize
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
+ objectOutputStream.writeObject(oidcPrincipal);
+ objectOutputStream.close();
+
+ //deserialize
+ byte[] bytes = byteArrayOutputStream.toByteArray();
+ ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
+ ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
+ OidcPrincipal deserializedOidcPrincipal = (OidcPrincipal)objectInputStream.readObject();
+ OidcSecurityContext deserializedOidcSecurityContext = deserializedOidcPrincipal.getOidcSecurityContext();
+ AccessToken deserializedAccessToken = deserializedOidcSecurityContext.getToken();
+
+ assertEquals(accessTokenString, deserializedOidcSecurityContext.getTokenString());
+ assertEquals(KeycloakConfiguration.ALICE, deserializedOidcPrincipal.getName());
+ assertEquals(KeycloakConfiguration.ALICE, deserializedAccessToken.getPreferredUsername());
+ assertEquals("alice@gmail.com", deserializedAccessToken.getEmail());
+ assertEquals(TEST_REALM, deserializedOidcSecurityContext.getRealm());
+ objectInputStream.close();
+ }
+
@Test
public void testSucessfulAuthenticationWithAuthServerUrl() throws Exception {
performBearerAuthentication(getOidcConfigurationInputStream(), SECURED_ENDPOINT, KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
diff --git a/http/oidc/src/test/java/org/wildfly/security/http/oidc/IDTokenTest.java b/http/oidc/src/test/java/org/wildfly/security/http/oidc/IDTokenTest.java
new file mode 100644
index 00000000000..3678d433247
--- /dev/null
+++ b/http/oidc/src/test/java/org/wildfly/security/http/oidc/IDTokenTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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 jakarta.json.Json;
+import jakarta.json.JsonObject;
+import org.jose4j.jwt.JwtClaims;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.wildfly.common.Assert.assertNotNull;
+
+/**
+ * Tests for ID Token.
+ */
+public class IDTokenTest {
+
+ @Test
+ public void testIDTokenWithAddressClaim() {
+ JwtClaims jwtClaims = new JwtClaims();
+ JsonObject jsonObject = Json.createObjectBuilder()
+ .add("address", Json.createObjectBuilder()
+ .add("region", "US")
+ .add("country", "New York")
+ .add("locality", "NY")
+ .add("postal_code", "10021"))
+ .build();
+ jwtClaims.setClaim("given_name", "Alice");
+ jwtClaims.setClaim("family_name", "Smith");
+ jwtClaims.setClaim("address", jsonObject.get("address"));
+ IDToken idToken = new IDToken(jwtClaims);
+ assertNotNull(idToken);
+ assertEquals("NY", idToken.getAddress().getLocality());
+ assertEquals("10021", idToken.getAddress().getPostalCode());
+ assertEquals("US", idToken.getAddress().getRegion());
+ assertEquals("New York", idToken.getAddress().getCountry());
+ assertEquals("Alice", idToken.getGivenName());
+ assertEquals("Smith", idToken.getFamilyName());
+ }
+}
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 bb3703dd682..8ebf4051bf2 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,9 +44,10 @@
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;
+
/**
* Keycloak configuration for testing.
*
@@ -50,6 +62,25 @@ public class KeycloakConfiguration {
private static final String BOB = "bob";
private static final String BOB_PASSWORD = "bob123+";
public static final String ALLOWED_ORIGIN = "http://somehost";
+ public static final boolean EMAIL_VERIFIED = false;
+ public static final String RSA_KEYSTORE_FILE_NAME = "jwt.keystore";
+ public static final String EC_KEYSTORE_FILE_NAME = "jwtEC.keystore";
+ public static final String KEYSTORE_ALIAS = "jwtKeystore";
+ public static final String KEYSTORE_PASS = "Elytron";
+ public static final String PKCS12_KEYSTORE_TYPE = "PKCS12";
+ public static String KEYSTORE_CLASSPATH;
+
+ /* Accepted Request Object Encrypting Algorithms for KeyCloak*/
+ public static final String RSA_OAEP = "RSA-OAEP";
+ public static final String RSA_OAEP_256 = "RSA-OAEP-256";
+ public static final String RSA1_5 = "RSA1_5";
+
+ /* Accepted Request Object Encryption Methods for KeyCloak*/
+ public static final String A128CBC_HS256 = "A128CBC-HS256";
+ public static final String A192CBC_HS384 = "A192CBC-HS384";
+ public static final String A256CBC_HS512 = "A256CBC-HS512";
+ public static CAGenerationTool caGenerationTool = null;
+ public X509Certificate caCertificate = null;
// the users below are for multi-tenancy tests specifically
public static final String TENANT1_USER = "tenant1_user";
@@ -73,20 +104,20 @@ public class KeycloakConfiguration {
*
*/
public static RealmRepresentation getRealmRepresentation(final String realmName, String clientId, String clientSecret,
- String clientHostName, int clientPort, String clientApp) {
- return createRealm(realmName, clientId, clientSecret, clientHostName, clientPort, clientApp);
+ 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 multiTenancyApp) {
- return createRealm(realmName, clientId, clientSecret, clientHostName, clientPort, clientApp, accessTokenLifespan, ssoSessionMaxLifespan, 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);
}
@@ -120,28 +151,36 @@ public static String getAccessToken(String authServerUrl, String realmName, Stri
.as(AccessTokenResponse.class).getToken();
}
+ private static RealmRepresentation createRealm(final String realmName, String clientId, String clientSecret,
+ String clientHostName, int clientPort, String clientApp,
+ boolean directAccessGrantEnabled, String bearerOnlyClientId,
+ 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) {
- return createRealm(name, clientId, clientSecret, clientHostName, clientPort, clientApp, false, null, null);
+ 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 multiTenancyApp) {
- return createRealm(name, clientId, clientSecret, clientHostName, clientPort, clientApp, false, null, null, accessTokenLifeSpan, ssoSessionMaxLifespan, 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) {
- return createRealm(name, clientId, clientSecret, clientHostName, clientPort, clientApp, directAccessGrantEnabled, bearerOnlyClientId, corsClientId, 3, 3, false);
+ String corsClientId, boolean configureClientScopes) throws Exception {
+ return createRealm(name, clientId, clientSecret, clientHostName, clientPort, clientApp, directAccessGrantEnabled, bearerOnlyClientId, corsClientId, 3, 3, configureClientScopes, false);
}
private static RealmRepresentation createRealm(String name, String clientId, String clientSecret,
String clientHostName, int clientPort, String clientApp,
boolean directAccessGrantEnabled, String bearerOnlyClientId,
- String corsClientId, int accessTokenLifespan, int ssoSessionMaxLifespan, boolean multiTenancyApp) {
+ String corsClientId, int accessTokenLifespan, int ssoSessionMaxLifespan,
+ boolean configureClientScopes, boolean multiTenancyApp) throws Exception {
RealmRepresentation realm = new RealmRepresentation();
realm.setRealm(name);
realm.setEnabled(true);
@@ -159,7 +198,12 @@ private static RealmRepresentation createRealm(String name, String clientId, Str
realm.getRoles().getRealm().add(new RoleRepresentation("user", null, false));
realm.getRoles().getRealm().add(new RoleRepresentation("admin", null, false));
- realm.getClients().add(createWebAppClient(clientId, clientSecret, clientHostName, clientPort, clientApp, directAccessGrantEnabled, multiTenancyApp));
+ ClientRepresentation webAppClient = createWebAppClient(clientId, clientSecret, clientHostName, clientPort, clientApp, directAccessGrantEnabled, multiTenancyApp);
+ if (configureClientScopes) {
+ webAppClient.setDefaultClientScopes(Collections.singletonList(OIDC_SCOPE));
+ webAppClient.setOptionalClientScopes(Arrays.asList("phone", "email", "profile"));
+ }
+ realm.getClients().add(webAppClient);
if (bearerOnlyClientId != null) {
realm.getClients().add(createBearerOnlyClient(bearerOnlyClientId));
@@ -185,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);
@@ -208,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;
}
@@ -229,6 +288,7 @@ private static UserRepresentation createUser(String username, String password, L
user.setCredentials(new ArrayList<>());
user.setRealmRoles(realmRoles);
user.setEmail(username + "@gmail.com");
+ user.setEmailVerified(EMAIL_VERIFIED);
user.setFirstName("Alice");
user.setLastName("Smith");
@@ -240,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/MockOidcClientConfiguration.java b/http/oidc/src/test/java/org/wildfly/security/http/oidc/MockOidcClientConfiguration.java
new file mode 100644
index 00000000000..b59d75a192f
--- /dev/null
+++ b/http/oidc/src/test/java/org/wildfly/security/http/oidc/MockOidcClientConfiguration.java
@@ -0,0 +1,167 @@
+/*
+ * 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 com.gargoylesoftware.htmlunit.TextPage;
+import io.restassured.RestAssured;
+import mockit.Mock;
+import mockit.MockUp;
+import mockit.integration.junit4.JMockit;
+import okhttp3.mockwebserver.MockWebServer;
+import org.apache.http.HttpStatus;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.wildfly.security.http.HttpServerAuthenticationMechanism;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.jose4j.jws.AlgorithmIdentifiers.HMAC_SHA256;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+import static org.wildfly.security.http.oidc.KeycloakConfiguration.ALICE;
+import static org.wildfly.security.http.oidc.KeycloakConfiguration.ALICE_PASSWORD;
+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.OIDC_NAME;
+import static org.wildfly.security.http.oidc.Oidc.OIDC_SCOPE;
+
+/**
+ * Tests for cases where the OpenID provider does not support
+ * request parameters when sending the request object as a JWT.
+ * The OidcClientConfiguration class is mocked to return values
+ * indicating a lack of support for request parameters.
+ *
+ * @author Prarthona Paul
+ */
+@RunWith(JMockit.class)
+public class MockOidcClientConfiguration extends OidcBaseTest {
+
+ @BeforeClass
+ public static void startTestContainers() throws Exception {
+ assumeTrue("Docker isn't available, OIDC tests will be skipped", isDockerAvailable());
+ KEYCLOAK_CONTAINER = new KeycloakContainer();
+ KEYCLOAK_CONTAINER.start();
+ sendRealmCreationRequest(KeycloakConfiguration.getRealmRepresentation(TEST_REALM, CLIENT_ID, CLIENT_SECRET, CLIENT_HOST_NAME, CLIENT_PORT, CLIENT_APP, false));
+ client = new MockWebServer();
+ client.start(CLIENT_PORT);
+ }
+
+ @AfterClass
+ public static void generalCleanup() throws Exception {
+ if (KEYCLOAK_CONTAINER != null) {
+ RestAssured
+ .given()
+ .auth().oauth2(KeycloakConfiguration.getAdminAccessToken(KEYCLOAK_CONTAINER.getAuthServerUrl()))
+ .when()
+ .delete(KEYCLOAK_CONTAINER.getAuthServerUrl() + "/admin/realms/" + TEST_REALM).then().statusCode(204);
+ KEYCLOAK_CONTAINER.stop();
+ }
+ if (client != null) {
+ client.shutdown();
+ }
+ }
+
+ @BeforeClass
+ public static void beforeClass() {
+ System.setProperty("oidc.provider.url", KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM);
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ System.clearProperty("oidc.provider.url");
+ }
+
+ @Test
+ public void testOidcWithRequestParameterUnsupported() throws Exception {
+ mockOidcClientConfig();
+ performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(REQUEST.getValue()), REQUEST.getValue());
+ }
+
+ @Test
+ public void testOidcWithRequestUriParameterUnsupported() throws Exception {
+ mockOidcClientConfig();
+ performAuthentication(getOidcConfigurationInputStreamWithRequestParameter(REQUEST_URI.getValue()), REQUEST_URI.getValue());
+ }
+
+ public void performAuthentication(InputStream oidcConfig, String requestFormat) throws Exception {
+ Map props = new HashMap<>();
+ OidcClientConfiguration oidcClientConfiguration = OidcClientConfigurationBuilder.build(oidcConfig);
+ assertEquals(OidcClientConfiguration.RelativeUrlsUsed.NEVER, oidcClientConfiguration.getRelativeUrls());
+ OidcClientContext oidcClientContext = new OidcClientContext(oidcClientConfiguration);
+ oidcFactory = new OidcMechanismFactory(oidcClientContext);
+ HttpServerAuthenticationMechanism mechanism = oidcFactory.createAuthenticationMechanism(OIDC_NAME, props, getCallbackHandler());
+
+ URI requestUri = new URI(getClientUrl());
+ TestingHttpServerRequest request = new TestingHttpServerRequest(null, requestUri);
+ mechanism.evaluateRequest(request);
+ TestingHttpServerResponse response = request.getResponse();
+ assertEquals(HttpStatus.SC_MOVED_TEMPORARILY, response.getStatusCode());
+ assertEquals(Status.NO_AUTH, request.getResult());
+ assertFalse(response.getFirstResponseHeaderValue("Location").contains(requestFormat + "="));
+ assertTrue(response.getFirstResponseHeaderValue("Location").contains("scope=" + OIDC_SCOPE + "+phone+profile+email")); //ALL scopes should be added to the URL directly
+
+ client.setDispatcher(createAppResponse(mechanism, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT));
+
+ TextPage page = loginToKeycloak(ALICE, ALICE_PASSWORD, requestUri, response.getLocation(),
+ response.getCookies()).click();
+ assertTrue(page.getContent().contains(CLIENT_PAGE_TEXT));
+ }
+
+
+ private void mockOidcClientConfig(){
+ new MockUp(){
+ // Used to indicate that the OpenID provider does not support request_uri parameter
+ @Mock
+ boolean getRequestUriParameterSupported(){
+ return false;
+ }
+
+ // Used to indicate that the OpenID provider does not support request parameter
+ @Mock
+ boolean getRequestParameterSupported(){
+ return false;
+ }
+ };
+ }
+
+ private InputStream getOidcConfigurationInputStreamWithRequestParameter(String requestParameter){
+ 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\" : \"" + HMAC_SHA256 + "\",\n" +
+ " \"scope\" : \"profile email phone\",\n" +
+ " \"credentials\" : {\n" +
+ " \"secret\" : \"" + CLIENT_SECRET + "\"\n" +
+ " }\n" +
+ "}";
+ return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8));
+ }
+}
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 fb9d8345431..6eb698160a0 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
@@ -38,6 +38,9 @@
import javax.security.sasl.AuthorizeCallback;
import org.apache.http.HttpStatus;
+import org.jose4j.jwt.JwtClaims;
+import org.jose4j.jwt.consumer.InvalidJwtException;
+import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import org.junit.AfterClass;
import org.keycloak.representations.idm.RealmRepresentation;
import org.testcontainers.DockerClientFactory;
@@ -46,6 +49,8 @@
import org.wildfly.security.auth.callback.IdentityCredentialCallback;
import org.wildfly.security.auth.callback.SecurityIdentityCallback;
import org.wildfly.security.auth.server.SecurityDomain;
+import org.wildfly.security.credential.BearerTokenCredential;
+import org.wildfly.security.credential.Credential;
import org.wildfly.security.evidence.Evidence;
import org.wildfly.security.http.HttpServerAuthenticationMechanism;
import org.wildfly.security.http.HttpServerAuthenticationMechanismFactory;
@@ -76,9 +81,10 @@
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";
public static final String TENANT1_REALM = "tenant1";
public static final String TENANT2_REALM = "tenant2";
public static final String KEYCLOAK_USERNAME = "username";
@@ -89,11 +95,18 @@ public class OidcBaseTest extends AbstractBaseHttpTest {
public static final String CLIENT_PAGE_TEXT = "Welcome page!";
public static final String CLIENT_HOST_NAME = "localhost";
public static MockWebServer client; // to simulate the application being secured
+ public static final Boolean CONFIGURE_CLIENT_SCOPES = true; // to simulate the application being secured
public static final String TENANT1_ENDPOINT = "tenant1";
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) {
@@ -132,12 +145,19 @@ protected static boolean isDockerAvailable() {
return false;
}
}
-
protected CallbackHandler getCallbackHandler() {
- return getCallbackHandler(null);
+ return getCallbackHandler(false, null, null);
}
protected CallbackHandler getCallbackHandler(String expectedPrincipal) {
+ return getCallbackHandler(false, null, expectedPrincipal);
+ }
+
+ protected CallbackHandler getCallbackHandler(boolean checkScope, String expectedScopes) {
+ return getCallbackHandler(checkScope, expectedScopes, null);
+ }
+
+ protected CallbackHandler getCallbackHandler(boolean checkScope, String expectedScopes, String expectedPrincipal) {
return callbacks -> {
for(Callback callback : callbacks) {
if (callback instanceof EvidenceVerifyCallback) {
@@ -149,7 +169,13 @@ protected CallbackHandler getCallbackHandler(String expectedPrincipal) {
} else if (callback instanceof AuthenticationCompleteCallback) {
// NO-OP
} else if (callback instanceof IdentityCredentialCallback) {
- // NO-OP
+ if (checkScope) {
+ try {
+ checkForScopeClaims(callback, expectedScopes);
+ } catch (InvalidJwtException e) {
+ throw new RuntimeException(e);
+ }
+ }
} else if (callback instanceof AuthorizeCallback) {
((AuthorizeCallback) callback).setAuthorized(true);
} else if (callback instanceof SecurityIdentityCallback) {
@@ -271,6 +297,7 @@ protected HtmlInput loginToKeycloak(String username, String password, URI reques
webClient.addCookie(getCookieString(cookie), requestUri.toURL(), null);
}
}
+
HtmlPage keycloakLoginPage = webClient.getPage(location);
HtmlForm loginForm = keycloakLoginPage.getForms().get(0);
loginForm.getInputByName(KEYCLOAK_USERNAME).setValueAttribute(username);
@@ -305,27 +332,43 @@ protected String getCookieString(HttpServerCookie cookie) {
return header.toString();
}
- protected void performAuthentication(InputStream oidcConfig, String username, String password, boolean loginToKeycloak,
- int expectedDispatcherStatusCode, String expectedLocation, String clientPageText) throws Exception {
- performAuthentication(oidcConfig, username, password, loginToKeycloak, expectedDispatcherStatusCode, getClientUrl(), expectedLocation, clientPageText);
+ protected void checkForScopeClaims(Callback callback, String expectedScopes) throws InvalidJwtException {
+ Credential credential = ((IdentityCredentialCallback)callback).getCredential();
+ String token = ((BearerTokenCredential) credential).getToken();
+ JwtClaims jwtClaims = new JwtConsumerBuilder().setSkipSignatureVerification().setSkipAllValidators().build().processToClaims(token);
+
+ if (expectedScopes != null) {
+ if (expectedScopes.contains("email")) {
+ assertTrue(jwtClaims.getClaimValueAsString("email_verified").contains(String.valueOf(KeycloakConfiguration.EMAIL_VERIFIED)));
+ }
+ if (expectedScopes.contains("profile")) {
+ assertTrue(jwtClaims.getClaimValueAsString("preferred_username").contains(KeycloakConfiguration.ALICE));
+ }
+ }
}
+ // Note: The tests will fail if `localhost` is not listed first in `/etc/hosts` file for the loopback addresses (IPv4 and IPv6).
protected void performAuthentication(InputStream oidcConfig, String username, String password, boolean loginToKeycloak,
- int expectedDispatcherStatusCode, String expectedLocation, String clientPageText,
- CallbackHandler callbackHandler) throws Exception {
- performAuthentication(oidcConfig, username, password, loginToKeycloak, expectedDispatcherStatusCode, getClientUrl(), expectedLocation, clientPageText,
- callbackHandler);
+ int expectedDispatcherStatusCode, String expectedLocation, String clientPageText) throws Exception {
+ performAuthentication(oidcConfig, username, password, loginToKeycloak, expectedDispatcherStatusCode, getClientUrl(), expectedLocation,
+ clientPageText, null, false);
}
protected void performAuthentication(InputStream oidcConfig, String username, String password, boolean loginToKeycloak,
int expectedDispatcherStatusCode, String clientUrl, String expectedLocation, String clientPageText) throws Exception {
- performAuthentication(oidcConfig, username, password, loginToKeycloak, expectedDispatcherStatusCode, clientUrl, expectedLocation, clientPageText,
- getCallbackHandler());
+ performAuthentication(oidcConfig, username, password, loginToKeycloak, expectedDispatcherStatusCode, clientUrl, expectedLocation,
+ clientPageText, null, false);
}
- protected void performAuthentication(InputStream oidcConfig, String username, String password, boolean loginToKeycloak,
- int expectedDispatcherStatusCode, String clientUrl, String expectedLocation, String clientPageText,
- CallbackHandler callbackHandler) throws Exception {
+ protected 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, getClientUrl(), expectedLocation, clientPageText,
+ expectedScope, checkInvalidScopeError);
+ }
+
+ private void performAuthentication(InputStream oidcConfig, String username, String password, boolean loginToKeycloak,
+ int expectedDispatcherStatusCode, String clientUrl, String expectedLocation, String clientPageText,
+ String expectedScope, boolean checkInvalidScopeError) throws Exception {
try {
Map props = new HashMap<>();
OidcClientConfiguration oidcClientConfiguration = OidcClientConfigurationBuilder.build(oidcConfig);
@@ -333,7 +376,12 @@ protected void performAuthentication(InputStream oidcConfig, String username, St
OidcClientContext oidcClientContext = new OidcClientContext(oidcClientConfiguration);
oidcFactory = new OidcMechanismFactory(oidcClientContext);
- HttpServerAuthenticationMechanism mechanism = oidcFactory.createAuthenticationMechanism(OIDC_NAME, props, callbackHandler);
+ HttpServerAuthenticationMechanism mechanism;
+ if (expectedScope == null) {
+ mechanism = oidcFactory.createAuthenticationMechanism(OIDC_NAME, props, getCallbackHandler());
+ } else {
+ mechanism = oidcFactory.createAuthenticationMechanism(OIDC_NAME, props, getCallbackHandler(true, expectedScope));
+ }
URI requestUri = new URI(clientUrl);
TestingHttpServerRequest request = new TestingHttpServerRequest(null, requestUri);
@@ -341,12 +389,22 @@ protected void performAuthentication(InputStream oidcConfig, String username, St
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 (loginToKeycloak) {
client.setDispatcher(createAppResponse(mechanism, expectedDispatcherStatusCode, expectedLocation, clientPageText));
- TextPage page = loginToKeycloak(username, password, requestUri, response.getLocation(),
- response.getCookies()).click();
- assertTrue(page.getContent().contains(clientPageText));
+
+ if (checkInvalidScopeError) {
+ WebClient webClient = getWebClient();
+ TextPage keycloakLoginPage = webClient.getPage(response.getLocation());
+ assertTrue(keycloakLoginPage.getWebResponse().getWebRequest().toString().contains("error_description=Invalid+scopes"));
+ } else {
+ TextPage page = loginToKeycloak(username, password, requestUri, response.getLocation(),
+ response.getCookies()).click();
+ assertTrue(page.getContent().contains(clientPageText));
+ }
}
} finally {
client.setDispatcher(new QueueDispatcher());
@@ -355,14 +413,14 @@ protected void performAuthentication(InputStream oidcConfig, String username, St
protected InputStream getOidcConfigurationInputStreamWithProviderUrl() {
String oidcConfig = "{\n" +
- " \"resource\" : \"" + CLIENT_ID + "\",\n" +
- " \"public-client\" : \"false\",\n" +
- " \"provider-url\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM + "\",\n" +
- " \"ssl-required\" : \"EXTERNAL\",\n" +
- " \"credentials\" : {\n" +
- " \"secret\" : \"" + CLIENT_SECRET + "\"\n" +
+ " \"" + Oidc.RESOURCE + "\" : \"" + CLIENT_ID + "\",\n" +
+ " \"" + Oidc.PUBLIC_CLIENT + "\" : \"false\",\n" +
+ " \"" + Oidc.PROVIDER_URL + "\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM + "\",\n" +
+ " \"" + Oidc.SSL_REQUIRED + "\" : \"EXTERNAL\",\n" +
+ " \"" + Oidc.CREDENTIALS + "\" : {\n" +
+ " \"" + Oidc.ClientCredentialsProviderType.SECRET.getValue() + "\" : \"" + CLIENT_SECRET + "\"\n" +
" }\n" +
"}";
return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8));
}
-}
\ No newline at end of file
+}
diff --git a/http/oidc/src/test/java/org/wildfly/security/http/oidc/OidcProviderMetadataTest.java b/http/oidc/src/test/java/org/wildfly/security/http/oidc/OidcProviderMetadataTest.java
new file mode 100644
index 00000000000..3773451f682
--- /dev/null
+++ b/http/oidc/src/test/java/org/wildfly/security/http/oidc/OidcProviderMetadataTest.java
@@ -0,0 +1,378 @@
+/*
+ * 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 org.wildfly.security.jose.util.JsonSerialization;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.List;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test OIDC json config class to return of values.
+ */
+public class OidcProviderMetadataTest {
+ private static OidcProviderMetadata oidcProviderMetadata;
+ private static OidcProviderMetadata emptyOidcProviderMetadata;
+ private static OidcProviderMetadata withoutOptionalsOidcProviderMetadata;
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ // load the control data
+ ByteArrayInputStream is = new ByteArrayInputStream(providerMetaData.getBytes());
+ oidcProviderMetadata = JsonSerialization.readValue(is, OidcProviderMetadata.class);
+ is.close();
+
+ // control data to check variable inits in OidcProviderMetadata
+ is = new ByteArrayInputStream(emptyProviderMetaData.getBytes());
+ emptyOidcProviderMetadata = JsonSerialization.readValue(is, OidcProviderMetadata.class);
+ is.close();
+
+ is = new ByteArrayInputStream(withoutOptionalsProviderMetaData.getBytes());
+ withoutOptionalsOidcProviderMetadata = JsonSerialization.readValue(is, OidcProviderMetadata.class);
+ is.close();
+ }
+
+ @Test
+ public void testIssuer() throws Exception {
+ assertTrue("http://localhost:8080/realms/myrealm".equals(oidcProviderMetadata.getIssuer()));
+ assertTrue("http://localhost:8080/realms/myrealm".equals(withoutOptionalsOidcProviderMetadata.getIssuer()));
+ assertNull(emptyOidcProviderMetadata.getIssuer());
+ }
+
+ @Test
+ public void testAuthorizationEndpoint() throws Exception {
+ assertTrue("http://localhost:8080/realms/myrealm/protocol/openid-connect/auth".equals(oidcProviderMetadata.getAuthorizationEndpoint()));
+ assertTrue("http://localhost:8080/auth".equals(withoutOptionalsOidcProviderMetadata.getAuthorizationEndpoint()));
+ assertNull(emptyOidcProviderMetadata.getAuthorizationEndpoint());
+ }
+
+ @Test
+ public void testTokenEndpoint() throws Exception {
+ assertTrue("http://localhost:8080/realms/myrealm/protocol/openid-connect/token".equals(oidcProviderMetadata.getTokenEndpoint()));
+ assertTrue("http://localhost:8080/token".equals(withoutOptionalsOidcProviderMetadata.getTokenEndpoint()));
+ assertNull(emptyOidcProviderMetadata.getTokenEndpoint());
+ }
+
+ @Test
+ public void testIntrospectionEndpoint() throws Exception {
+ assertTrue("http://localhost:8080/realms/myrealm/protocol/openid-connect/token/introspect".equals(oidcProviderMetadata.getIntrospectionEndpoint()));
+ assertTrue("http://localhost:8080/introspect".equals(withoutOptionalsOidcProviderMetadata.getIntrospectionEndpoint()));
+ assertNull(emptyOidcProviderMetadata.getIntrospectionEndpoint());
+ }
+
+ @Test
+ public void testUserinfoEndpoint() throws Exception {
+ assertTrue("http://localhost:8080/realms/myrealm/protocol/openid-connect/userinfo".equals(oidcProviderMetadata.getUserinfoEndpoint()));
+ assertTrue("http://localhost:8080/userinfo".equals(withoutOptionalsOidcProviderMetadata.getUserinfoEndpoint()));
+ assertNull(emptyOidcProviderMetadata.getUserinfoEndpoint());
+ }
+
+ @Test
+ public void testLogoutEndpoint() throws Exception {
+ assertTrue("http://localhost:8080/realms/myrealm/protocol/openid-connect/logout".equals(oidcProviderMetadata.getLogoutEndpoint()));
+ assertTrue("http://localhost:8080/logout".equals(withoutOptionalsOidcProviderMetadata.getLogoutEndpoint()));
+ assertNull(emptyOidcProviderMetadata.getLogoutEndpoint());
+ }
+
+ @Test
+ public void testJwksUri() throws Exception {
+ assertTrue("http://localhost:8080/realms/myrealm/protocol/openid-connect/certs".equals(oidcProviderMetadata.getJwksUri()));
+ assertTrue("http://localhost:8080/certs".equals(withoutOptionalsOidcProviderMetadata.getJwksUri()));
+ assertNull(emptyOidcProviderMetadata.getJwksUri());
+ }
+
+ @Test
+ public void testCheckSessionIframe() throws Exception {
+ assertTrue("http://localhost:8080/realms/myrealm/protocol/openid-connect/login-status-iframe.html".equals(oidcProviderMetadata.getCheckSessionIframe()));
+ assertTrue("http://localhost:8080/login-status-iframe.html".equals(withoutOptionalsOidcProviderMetadata.getCheckSessionIframe()));
+ assertNull(emptyOidcProviderMetadata.getCheckSessionIframe());
+ }
+
+ @Test
+ public void testGrantTypesSupported() throws Exception {
+ List l = oidcProviderMetadata.getGrantTypesSupported();
+ assertTrue(l.contains("refresh_token"));
+ assertNull(emptyOidcProviderMetadata.getGrantTypesSupported());
+ }
+
+ @Test
+ public void testResponseTypesSupported() throws Exception {
+ List l = oidcProviderMetadata.getResponseTypesSupported();
+ assertTrue(l.contains("code id_token"));
+ assertNull(emptyOidcProviderMetadata.getResponseTypesSupported());
+ }
+
+ @Test
+ public void testSubjectTypesSupported() throws Exception {
+ List l = oidcProviderMetadata.getSubjectTypesSupported();
+ assertTrue(l.contains("pairwise"));
+ assertNull(emptyOidcProviderMetadata.getSubjectTypesSupported());
+ }
+
+ @Test
+ public void testIdTokenSigningAlgValuesSupported() throws Exception {
+ List l = oidcProviderMetadata.getIdTokenSigningAlgValuesSupported();
+ assertTrue(l.contains("HS256"));
+ assertNull(emptyOidcProviderMetadata.getIdTokenSigningAlgValuesSupported());
+ }
+
+ @Test
+ public void testIdTokenEncryptionAlgValuesSupported() throws Exception {
+ List l = oidcProviderMetadata.getIdTokenEncryptionAlgValuesSupported();
+ assertTrue(l.contains("RSA1_5"));
+ assertNull(emptyOidcProviderMetadata.getIdTokenEncryptionAlgValuesSupported());
+ }
+
+ @Test
+ public void testIdTokenEncryptionEncValuesSupported() throws Exception {
+ List l = oidcProviderMetadata.getIdTokenEncryptionEncValuesSupported();
+ assertTrue(l.contains("A128CBC-HS256"));
+ assertNull(emptyOidcProviderMetadata.getIdTokenEncryptionEncValuesSupported());
+ }
+
+ @Test
+ public void testUserInfoSigningAlgValuesSupported() throws Exception {
+ List l = oidcProviderMetadata.getUserInfoSigningAlgValuesSupported();
+ assertTrue(l.contains("EdDSA"));
+ assertNull(emptyOidcProviderMetadata.getUserInfoSigningAlgValuesSupported());
+ }
+
+ @Test
+ public void testRequestObjectSigningAlgValuesSupported() throws Exception {
+ List l = oidcProviderMetadata.getRequestObjectSigningAlgValuesSupported();
+ assertTrue(l.contains("RS384"));
+ assertNull(emptyOidcProviderMetadata.getRequestObjectSigningAlgValuesSupported());
+ assertNull(withoutOptionalsOidcProviderMetadata.getRequestObjectSigningAlgValuesSupported());
+ }
+
+ @Test
+ public void testResponseModesSupported() throws Exception {
+ List l = oidcProviderMetadata.getResponseModesSupported();
+ assertTrue(l.contains("query.jwt"));
+ assertNull(emptyOidcProviderMetadata.getResponseModesSupported());
+ }
+
+ @Test
+ public void testRegistrationEndpoint() throws Exception {
+ assertTrue("http://localhost:8080/realms/myrealm/clients-registrations/openid-connect".equals(oidcProviderMetadata.getRegistrationEndpoint()));
+ assertTrue("http://localhost:8080/openid-connect".equals(withoutOptionalsOidcProviderMetadata.getRegistrationEndpoint()));
+ assertNull(emptyOidcProviderMetadata.getRegistrationEndpoint());
+ }
+
+ @Test
+ public void testTokenEndpointAuthMethodsSupported() throws Exception {
+ List l = oidcProviderMetadata.getTokenEndpointAuthMethodsSupported();
+ assertTrue(l.contains("client_secret_basic"));
+ assertNull(emptyOidcProviderMetadata.getTokenEndpointAuthMethodsSupported());
+ }
+
+ @Test
+ public void testTokenEndpointAuthSigningAlgValuesSupported() throws Exception {
+ List l = oidcProviderMetadata.getTokenEndpointAuthSigningAlgValuesSupported();
+ assertTrue(l.contains("PS384"));
+ assertNull(emptyOidcProviderMetadata.getTokenEndpointAuthSigningAlgValuesSupported());
+ }
+
+ @Test
+ public void testClaimsSupported() throws Exception {
+ List l = oidcProviderMetadata.getClaimsSupported();
+ assertTrue(l.contains("given_name"));
+ assertNull(emptyOidcProviderMetadata.getClaimsSupported());
+ }
+
+ @Test
+ public void testClaimTypesSupported() throws Exception {
+ List l = oidcProviderMetadata.getClaimTypesSupported();
+ assertTrue(l.contains("normal"));
+ assertNull(emptyOidcProviderMetadata.getClaimTypesSupported());
+ }
+
+ @Test
+ public void testClaimsParameterSupported() throws Exception {
+ assertTrue(oidcProviderMetadata.getClaimsParameterSupported());
+ assertFalse(withoutOptionalsOidcProviderMetadata.getClaimsParameterSupported());
+ }
+
+ @Test
+ public void testScopesSupported() throws Exception {
+ List l = oidcProviderMetadata.getScopesSupported();
+ assertTrue(l.contains("offline_access"));
+ assertNull(emptyOidcProviderMetadata.getScopesSupported());
+ }
+
+ @Test
+ public void testRequestParameterSupported() throws Exception {
+ assertTrue(oidcProviderMetadata.getRequestParameterSupported());
+ assertFalse(withoutOptionalsOidcProviderMetadata.getRequestParameterSupported());
+ }
+
+ @Test
+ public void testRequestUriParameterSupported() throws Exception {
+ assertTrue(oidcProviderMetadata.getRequestUriParameterSupported());
+ assertFalse(withoutOptionalsOidcProviderMetadata.getRequestUriParameterSupported());
+ }
+
+ @Test
+ public void testPushedAuthorizationRequestEndpoint() throws Exception {
+ assertTrue("http://localhost:8080/realms/myrealm/protocol/openid-connect/ext/par/request".equals(oidcProviderMetadata.getPushedAuthorizationRequestEndpoint()));
+ assertNull(emptyOidcProviderMetadata.getPushedAuthorizationRequestEndpoint());
+ assertNull(withoutOptionalsOidcProviderMetadata.getPushedAuthorizationRequestEndpoint());
+ }
+
+ @Test
+ public void testRevocationEndpoint() throws Exception {
+ assertTrue("http://localhost:8080/realms/myrealm/protocol/openid-connect/revoke".equals(oidcProviderMetadata.getRevocationEndpoint()));
+ assertTrue("http://localhost:8080/revoke".equals(withoutOptionalsOidcProviderMetadata.getRevocationEndpoint()));
+ assertNull(emptyOidcProviderMetadata.getRevocationEndpoint());
+ }
+
+ @Test
+ public void testRevocationEndpointAuthMethodsSupported() throws Exception {
+ List l = oidcProviderMetadata.getRevocationEndpointAuthMethodsSupported();
+ assertTrue(l.contains("client_secret_basic"));
+ assertNull(emptyOidcProviderMetadata.getRevocationEndpointAuthMethodsSupported());
+ }
+
+ @Test
+ public void testRevocationEndpointAuthSigningAlgValuesSupported() throws Exception {
+ List l = oidcProviderMetadata.getRevocationEndpointAuthSigningAlgValuesSupported();
+ assertTrue(l.contains("RS384"));
+ assertNull(emptyOidcProviderMetadata.getRevocationEndpointAuthSigningAlgValuesSupported());
+ }
+
+ @Test
+ public void testBackchannelLogoutSupported() throws Exception {
+ assertTrue(oidcProviderMetadata.getBackchannelLogoutSupported());
+ assertFalse(withoutOptionalsOidcProviderMetadata.getBackchannelLogoutSupported());
+ }
+
+ @Test
+ public void testBackchannelLogoutSessionSupported() throws Exception {
+ assertTrue(oidcProviderMetadata.getBackchannelLogoutSessionSupported());
+ assertFalse(withoutOptionalsOidcProviderMetadata.getBackchannelLogoutSessionSupported());
+ }
+
+ @Test
+ public void testCodeChallengeMethodsSupported() throws Exception {
+ List l = oidcProviderMetadata.getCodeChallengeMethodsSupported();
+ assertTrue(l.contains("S256"));
+ assertNull(emptyOidcProviderMetadata.getCodeChallengeMethodsSupported());
+ }
+
+ @Test
+ public void testTlsClientCertificateBoundAccessTokens() throws Exception {
+ assertTrue(oidcProviderMetadata.getTlsClientCertificateBoundAccessTokens());
+ assertFalse(withoutOptionalsOidcProviderMetadata.getTlsClientCertificateBoundAccessTokens());
+ }
+
+ @Test
+ public void testRequestObjectEncryptionEncValuesSupported() throws Exception {
+ List l = oidcProviderMetadata.getRequestObjectEncryptionEncValuesSupported();
+ assertTrue(l.contains("A192GCM"));
+ assertNull(emptyOidcProviderMetadata.getRequestObjectEncryptionEncValuesSupported());
+ assertNull(withoutOptionalsOidcProviderMetadata.getRequestObjectEncryptionEncValuesSupported());
+ }
+
+ @Test
+ public void testRequestObjectEncryptionAlgValuesSupported() throws Exception {
+ List l = oidcProviderMetadata.getRequestObjectEncryptionAlgValuesSupported();
+ assertTrue(l.contains("RSA1_5"));
+ assertNull(emptyOidcProviderMetadata.getRequestObjectEncryptionAlgValuesSupported());
+ assertNull(withoutOptionalsOidcProviderMetadata.getRequestObjectEncryptionAlgValuesSupported());
+ }
+
+ // Control data taken from keycloak
+ private static final String providerMetaData = "{\n" +
+ "\"issuer\":\"http://localhost:8080/realms/myrealm\"\n" +
+ ",\"authorization_endpoint\":\"http://localhost:8080/realms/myrealm/protocol/openid-connect/auth\"\n" +
+ ",\"token_endpoint\":\"http://localhost:8080/realms/myrealm/protocol/openid-connect/token\"\n" +
+ ",\"introspection_endpoint\":\"http://localhost:8080/realms/myrealm/protocol/openid-connect/token/introspect\"\n" +
+ ",\"userinfo_endpoint\":\"http://localhost:8080/realms/myrealm/protocol/openid-connect/userinfo\"\n" +
+ ",\"end_session_endpoint\":\"http://localhost:8080/realms/myrealm/protocol/openid-connect/logout\"\n" +
+ ",\"jwks_uri\":\"http://localhost:8080/realms/myrealm/protocol/openid-connect/certs\"\n" +
+ ",\"check_session_iframe\":\"http://localhost:8080/realms/myrealm/protocol/openid-connect/login-status-iframe.html\"\n" +
+ ",\"grant_types_supported\":[\"authorization_code\",\"implicit\",\"refresh_token\",\"password\",\"client_credentials\",\"urn:openid:params:grant-type:ciba\",\"urn:ietf:params:oauth:grant-type:device_code\"]\n" +
+ ",\"response_types_supported\":[\"code\",\"none\",\"id_token\",\"token\",\"id_token token\",\"code id_token\",\"code token\",\"code id_token token\"]\n" +
+ ",\"subject_types_supported\":[\"public\",\"pairwise\"]\n" +
+ ",\"id_token_signing_alg_values_supported\":[\"PS384\",\"RS384\",\"EdDSA\",\"ES384\",\"HS256\",\"HS512\",\"ES256\",\"RS256\",\"HS384\",\"ES512\",\"PS256\",\"PS512\",\"RS512\"]\n" +
+ ",\"id_token_encryption_alg_values_supported\":[\"RSA-OAEP\",\"RSA-OAEP-256\",\"RSA1_5\"]\n" +
+ ",\"id_token_encryption_enc_values_supported\":[\"A256GCM\",\"A192GCM\",\"A128GCM\",\"A128CBC-HS256\",\"A192CBC-HS384\",\"A256CBC-HS512\"]\n" +
+ ",\"userinfo_signing_alg_values_supported\":[\"PS384\",\"RS384\",\"EdDSA\",\"ES384\",\"HS256\",\"HS512\",\"ES256\",\"RS256\",\"HS384\",\"ES512\",\"PS256\",\"PS512\",\"RS512\",\"none\"]\n" +
+ ",\"request_object_signing_alg_values_supported\":[\"PS384\",\"RS384\",\"EdDSA\",\"ES384\",\"HS256\",\"HS512\",\"ES256\",\"RS256\",\"HS384\",\"ES512\",\"PS256\",\"PS512\",\"RS512\",\"none\"]\n" +
+ ",\"response_modes_supported\":[\"query\",\"fragment\",\"form_post\",\"query.jwt\",\"fragment.jwt\",\"form_post.jwt\",\"jwt\"]\n" +
+ ",\"registration_endpoint\":\"http://localhost:8080/realms/myrealm/clients-registrations/openid-connect\"\n" +
+ ",\"token_endpoint_auth_methods_supported\":[\"private_key_jwt\",\"client_secret_basic\",\"client_secret_post\",\"tls_client_auth\",\"client_secret_jwt\"]\n" +
+ ",\"token_endpoint_auth_signing_alg_values_supported\":[\"PS384\",\"RS384\",\"EdDSA\",\"ES384\",\"HS256\",\"HS512\",\"ES256\",\"RS256\",\"HS384\",\"ES512\",\"PS256\",\"PS512\",\"RS512\"]\n" +
+ ",\"claims_supported\":[\"aud\",\"sub\",\"iss\",\"auth_time\",\"name\",\"given_name\",\"family_name\",\"preferred_username\",\"email\",\"acr\"]\n" +
+ ",\"claim_types_supported\":[\"normal\"]\n" +
+ ",\"claims_parameter_supported\":true\n" +
+ ",\"scopes_supported\":[\"openid\",\"address\",\"profile\",\"offline_access\",\"microprofile-jwt\",\"acr\",\"web-origins\",\"basic\",\"email\",\"roles\",\"phone\"]\n" +
+ ",\"request_parameter_supported\":true\n" +
+ ",\"request_uri_parameter_supported\":true\n" +
+ ",\"pushed_authorization_request_endpoint\":\"http://localhost:8080/realms/myrealm/protocol/openid-connect/ext/par/request\"\n" +
+ ",\"revocation_endpoint\":\"http://localhost:8080/realms/myrealm/protocol/openid-connect/revoke\"\n" +
+ ",\"revocation_endpoint_auth_methods_supported\":[\"private_key_jwt\",\"client_secret_basic\",\"client_secret_post\",\"tls_client_auth\",\"client_secret_jwt\"]\n" +
+ ",\"revocation_endpoint_auth_signing_alg_values_supported\":[\"PS384\",\"RS384\",\"EdDSA\",\"ES384\",\"HS256\",\"HS512\",\"ES256\",\"RS256\",\"HS384\",\"ES512\",\"PS256\",\"PS512\",\"RS512\"]\n" +
+ ",\"backchannel_logout_supported\":true\n" +
+ ",\"backchannel_logout_session_supported\":true\n" +
+ ",\"code_challenge_methods_supported\":[\"plain\",\"S256\"]\n" +
+ ",\"tls_client_certificate_bound_access_tokens\":true\n" +
+ ",\"request_object_encryption_enc_values_supported\":[\"A256GCM\",\"A192GCM\",\"A128GCM\",\"A128CBC-HS256\",\"A192CBC-HS384\",\"A256CBC-HS512\"]\n" +
+ ",\"request_object_encryption_alg_values_supported\":[\"RSA-OAEP\",\"RSA-OAEP-256\",\"RSA1_5\"]\n" +
+ "}";
+
+ private static final String emptyProviderMetaData = "{}";
+
+ private static final String withoutOptionalsProviderMetaData = "{\n" +
+ "\"issuer\":\"http://localhost:8080/realms/myrealm\"\n" +
+ ",\"authorization_endpoint\":\"http://localhost:8080/auth\"\n" +
+ ",\"token_endpoint\":\"http://localhost:8080/token\"\n" +
+ ",\"introspection_endpoint\":\"http://localhost:8080/introspect\"\n" +
+ ",\"userinfo_endpoint\":\"http://localhost:8080/userinfo\"\n" +
+ ",\"end_session_endpoint\":\"http://localhost:8080/logout\"\n" +
+ ",\"jwks_uri\":\"http://localhost:8080/certs\"\n" +
+ ",\"check_session_iframe\":\"http://localhost:8080/login-status-iframe.html\"\n" +
+ ",\"grant_types_supported\":[\"authorization_code\",\"implicit\"]\n" +
+ ",\"response_types_supported\":[\"code\",\"none\",\"id_token\",\"token\"]\n" +
+ ",\"subject_types_supported\":[\"public\",\"pairwise\"]\n" +
+ ",\"id_token_signing_alg_values_supported\":[\"PS384\"]\n" +
+ ",\"id_token_encryption_alg_values_supported\":[\"RSA-OAEP\",\"RSA-OAEP-256\",\"RSA1_5\"]\n" +
+ ",\"id_token_encryption_enc_values_supported\":[\"A256GCM\"]\n" +
+ ",\"userinfo_signing_alg_values_supported\":[\"PS384\",\"none\"]\n" +
+ ",\"response_modes_supported\":[\"query\",\"fragment\",\"form_post.jwt\",\"jwt\"]\n" +
+ ",\"registration_endpoint\":\"http://localhost:8080/openid-connect\"\n" +
+ ",\"token_endpoint_auth_methods_supported\":[\"private_key_jwt\",\"client_secret_basic\"]\n" +
+ ",\"token_endpoint_auth_signing_alg_values_supported\":[\"PS384\",\"RS384\"]\n" +
+ ",\"claims_supported\":[\"aud\",\"sub\"]\n" +
+ ",\"claim_types_supported\":[\"normal\"]\n" +
+ ",\"scopes_supported\":[\"openid\",\"address\",\"profile\"]\n" +
+ ",\"revocation_endpoint\":\"http://localhost:8080/revoke\"\n" +
+ ",\"revocation_endpoint_auth_methods_supported\":[\"private_key_jwt\"]\n" +
+ ",\"revocation_endpoint_auth_signing_alg_values_supported\":[\"PS384\",\"RS384\",\"EdDSA\"]\n" +
+ ",\"code_challenge_methods_supported\":[\"plain\",\"S256\"]\n" +
+ "}";
+}
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 a9b13687551..4dede8b5ed6 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;
@@ -30,7 +44,31 @@
import static org.wildfly.security.http.oidc.KeycloakConfiguration.TENANT1_USER;
import static org.wildfly.security.http.oidc.KeycloakConfiguration.TENANT2_PASSWORD;
import static org.wildfly.security.http.oidc.KeycloakConfiguration.TENANT2_USER;
+import static org.wildfly.security.http.oidc.Oidc.AUTH_SERVER_URL;
+import static org.wildfly.security.http.oidc.Oidc.AUTHENTICATION_REQUEST_FORMAT;
+import static org.wildfly.security.http.oidc.Oidc.CREDENTIALS;
+import static org.wildfly.security.http.oidc.Oidc.ClientCredentialsProviderType;
+import static org.wildfly.security.http.oidc.Oidc.PROVIDER_URL;
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.PUBLIC_CLIENT;
+import static org.wildfly.security.http.oidc.Oidc.PRINCIPAL_ATTRIBUTE;
+import static org.wildfly.security.http.oidc.Oidc.REQUEST_OBJECT_SIGNING_ALGORITHM;
+import static org.wildfly.security.http.oidc.Oidc.REQUEST_OBJECT_SIGNING_KEYSTORE_FILE;
+import static org.wildfly.security.http.oidc.Oidc.REQUEST_OBJECT_SIGNING_KEYSTORE_PASSWORD;
+import static org.wildfly.security.http.oidc.Oidc.REQUEST_OBJECT_SIGNING_KEYSTORE_TYPE;
+import static org.wildfly.security.http.oidc.Oidc.REQUEST_OBJECT_SIGNING_KEY_PASSWORD;
+import static org.wildfly.security.http.oidc.Oidc.REQUEST_OBJECT_SIGNING_KEY_ALIAS;
+import static org.wildfly.security.http.oidc.Oidc.REQUEST_OBJECT_ENCRYPTION_ALG_VALUE;
+import static org.wildfly.security.http.oidc.Oidc.REQUEST_OBJECT_ENCRYPTION_ENC_VALUE;
+import static org.wildfly.security.http.oidc.Oidc.RESOURCE;
+import static org.wildfly.security.http.oidc.Oidc.REALM;
+import static org.wildfly.security.http.oidc.Oidc.SCOPE;
+import static org.wildfly.security.http.oidc.Oidc.SSL_REQUIRED;
+import static org.wildfly.security.http.oidc.Oidc.TOKEN_SIGNATURE_ALGORITHM;
+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;
@@ -39,19 +77,20 @@
import java.util.HashMap;
import java.util.Map;
-import org.apache.http.HttpStatus;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.wildfly.security.http.HttpServerAuthenticationMechanism;
+import javax.security.auth.callback.CallbackHandler;
+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.
@@ -69,9 +108,10 @@ public static void startTestContainers() throws Exception {
assumeTrue("Docker isn't available, OIDC tests will be skipped", isDockerAvailable());
KEYCLOAK_CONTAINER = new KeycloakContainer();
KEYCLOAK_CONTAINER.start();
- sendRealmCreationRequest(KeycloakConfiguration.getRealmRepresentation(TEST_REALM, CLIENT_ID, CLIENT_SECRET, CLIENT_HOST_NAME, CLIENT_PORT, CLIENT_APP));
- sendRealmCreationRequest(KeycloakConfiguration.getRealmRepresentation(TENANT1_REALM, CLIENT_ID, CLIENT_SECRET, CLIENT_HOST_NAME, CLIENT_PORT, CLIENT_APP, ACCESS_TOKEN_LIFESPAN, SESSION_MAX_LIFESPAN, true));
- sendRealmCreationRequest(KeycloakConfiguration.getRealmRepresentation(TENANT2_REALM, CLIENT_ID, CLIENT_SECRET, CLIENT_HOST_NAME, CLIENT_PORT, CLIENT_APP, ACCESS_TOKEN_LIFESPAN, SESSION_MAX_LIFESPAN, true));
+ sendRealmCreationRequest(KeycloakConfiguration.getRealmRepresentation(TEST_REALM, CLIENT_ID, CLIENT_SECRET, CLIENT_HOST_NAME, CLIENT_PORT, CLIENT_APP, false));
+ sendRealmCreationRequest(KeycloakConfiguration.getRealmRepresentation(TEST_REALM_WITH_SCOPES, CLIENT_ID, CLIENT_SECRET, CLIENT_HOST_NAME, CLIENT_PORT, CLIENT_APP, CONFIGURE_CLIENT_SCOPES));
+ sendRealmCreationRequest(KeycloakConfiguration.getRealmRepresentation(TENANT1_REALM, CLIENT_ID, CLIENT_SECRET, CLIENT_HOST_NAME, CLIENT_PORT, CLIENT_APP, ACCESS_TOKEN_LIFESPAN, SESSION_MAX_LIFESPAN, false, true));
+ sendRealmCreationRequest(KeycloakConfiguration.getRealmRepresentation(TENANT2_REALM, CLIENT_ID, CLIENT_SECRET, CLIENT_HOST_NAME, CLIENT_PORT, CLIENT_APP, ACCESS_TOKEN_LIFESPAN, SESSION_MAX_LIFESPAN, false, true));
client = new MockWebServer();
client.start(CLIENT_PORT);
}
@@ -84,6 +124,11 @@ public static void generalCleanup() throws Exception {
.auth().oauth2(KeycloakConfiguration.getAdminAccessToken(KEYCLOAK_CONTAINER.getAuthServerUrl()))
.when()
.delete(KEYCLOAK_CONTAINER.getAuthServerUrl() + "/admin/realms/" + TEST_REALM).then().statusCode(204);
+ RestAssured
+ .given()
+ .auth().oauth2(KeycloakConfiguration.getAdminAccessToken(KEYCLOAK_CONTAINER.getAuthServerUrl()))
+ .when()
+ .delete(KEYCLOAK_CONTAINER.getAuthServerUrl() + "/admin/realms/" + TEST_REALM_WITH_SCOPES).then().statusCode(204);
RestAssured
.given()
.auth().oauth2(KeycloakConfiguration.getAdminAccessToken(KEYCLOAK_CONTAINER.getAuthServerUrl()))
@@ -187,23 +232,139 @@ public void testTokenSignatureAlgorithm() throws Exception {
performAuthentication(getOidcConfigurationInputStreamWithTokenSignatureAlgorithm(), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT);
}
+ @Test
+ public void testInvalidScope() throws Exception {
+ String expectedScope = OIDC_SCOPE + "+INVALID_SCOPE";
+ performAuthentication(getOidcConfigurationInputStreamWithScope("INVALID_SCOPE"), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), "error=invalid_scope", expectedScope, true);
+ }
@Test
- public void testPrincipalAttribute() throws Exception {
- // custom principal-attribute
- performAuthentication(getOidcConfigurationInputStreamWithPrincipalAttribute("aud"), KeycloakConfiguration.ALICE,
- KeycloakConfiguration.ALICE_PASSWORD, true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT,
- getCallbackHandler("test-webapp"));
+ public void testEmptyScope() throws Exception {
+ performAuthentication(getOidcConfigurationInputStreamWithScope(""), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT, OIDC_SCOPE, false);
+ }
+
+ @Test
+ public void testSingleScopeValue() throws Exception {
+ String expectedScope = OIDC_SCOPE + "+profile";
+ performAuthentication(getOidcConfigurationInputStreamWithScope("profile"), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT, expectedScope, false);
+ }
+
+ @Test
+ public void testMultipleScopeValue() throws Exception {
+ String expectedScope = OIDC_SCOPE + "+phone+profile+email";
+ performAuthentication(getOidcConfigurationInputStreamWithScope("email phone profile"), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT, expectedScope, false);
+ }
- // standard principal-attribute
- performAuthentication(getOidcConfigurationInputStreamWithPrincipalAttribute("given_name"), KeycloakConfiguration.ALICE,
- KeycloakConfiguration.ALICE_PASSWORD, true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT,
- getCallbackHandler("Alice"));
+ @Test
+ public void testOpenIDScopeValue() throws Exception {
+ String expectedScope = OIDC_SCOPE;
+ performAuthentication(getOidcConfigurationInputStreamWithScope(OIDC_SCOPE), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT, expectedScope, false);
+ }
- // invalid principal-attribute, logging in should still succeed
- performAuthentication(getOidcConfigurationInputStreamWithPrincipalAttribute("invalid_claim"), KeycloakConfiguration.ALICE,
- KeycloakConfiguration.ALICE_PASSWORD, true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT,
- getCallbackHandler());
+ @Test
+ public void testOpenIDWithMultipleScopeValue() throws Exception {
+ String expectedScope = OIDC_SCOPE + "+phone+profile+email";//order gets changed when combining with query parameters
+ performAuthentication(getOidcConfigurationInputStreamWithScope("email phone profile " + OIDC_SCOPE), KeycloakConfiguration.ALICE, KeycloakConfiguration.ALICE_PASSWORD,
+ 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);
}
/*****************************************************************************************************************************************
@@ -381,6 +542,37 @@ private void testNonExistingUser(String username, String password, String tenant
assertTrue(page.getBody().asText().contains("Invalid username or password"));
}
+ private void loginToAppMultiTenancy(InputStream oidcConfig, String username, String password, boolean loginToKeycloak,
+ int expectedDispatcherStatusCode, String expectedLocation, String clientPageText,
+ CallbackHandler callbackHandler) throws Exception {
+ try {
+ Map props = new HashMap<>();
+ OidcClientConfiguration oidcClientConfiguration = OidcClientConfigurationBuilder.build(oidcConfig);
+ assertEquals(OidcClientConfiguration.RelativeUrlsUsed.NEVER, oidcClientConfiguration.getRelativeUrls());
+
+ OidcClientContext oidcClientContext = new OidcClientContext(oidcClientConfiguration);
+ oidcFactory = new OidcMechanismFactory(oidcClientContext);
+
+ HttpServerAuthenticationMechanism mechanism = oidcFactory.createAuthenticationMechanism(OIDC_NAME, props, callbackHandler);
+
+ URI requestUri = new URI(getClientUrl());
+ TestingHttpServerRequest request = new TestingHttpServerRequest(null, requestUri);
+ mechanism.evaluateRequest(request);
+ TestingHttpServerResponse response = request.getResponse();
+ assertEquals(loginToKeycloak ? HttpStatus.SC_MOVED_TEMPORARILY : HttpStatus.SC_FORBIDDEN, response.getStatusCode());
+ assertEquals(Status.NO_AUTH, request.getResult());
+
+ if (loginToKeycloak) {
+ client.setDispatcher(createAppResponse(mechanism, expectedDispatcherStatusCode, expectedLocation, clientPageText));
+ TextPage page = loginToKeycloak(username, password, requestUri, response.getLocation(),
+ response.getCookies()).click();
+ assertTrue(page.getContent().contains(clientPageText));
+ }
+ } finally {
+ client.setDispatcher(new QueueDispatcher());
+ }
+ }
+
private void performTenantRequestWithAuthServerUrl(String username, String password, String tenant, String otherTenant) throws Exception {
performTenantRequest(username, password, tenant, otherTenant, true);
}
@@ -434,6 +626,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);
}
@@ -444,13 +684,13 @@ private InputStream getOidcConfigurationInputStream(String clientSecret) {
private InputStream getOidcConfigurationInputStream(String clientSecret, String authServerUrl) {
String oidcConfig = "{\n" +
- " \"realm\" : \"" + TEST_REALM + "\",\n" +
- " \"resource\" : \"" + CLIENT_ID + "\",\n" +
- " \"public-client\" : \"false\",\n" +
- " \"auth-server-url\" : \"" + authServerUrl + "\",\n" +
- " \"ssl-required\" : \"EXTERNAL\",\n" +
- " \"credentials\" : {\n" +
- " \"secret\" : \"" + clientSecret + "\"\n" +
+ " \"" + REALM + "\" : \"" + TEST_REALM + "\",\n" +
+ " \"" + RESOURCE + "\" : \"" + CLIENT_ID + "\",\n" +
+ " \"" + PUBLIC_CLIENT + "\" : \"false\",\n" +
+ " \"" + AUTH_SERVER_URL + "\" : \"" + authServerUrl + "\",\n" +
+ " \"" + SSL_REQUIRED + "\" : \"EXTERNAL\",\n" +
+ " \"" + CREDENTIALS + "\" : {\n" +
+ " \"" + ClientCredentialsProviderType.SECRET.getValue() + "\" : \"" + clientSecret + "\"\n" +
" }\n" +
"}";
return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8));
@@ -458,12 +698,12 @@ private InputStream getOidcConfigurationInputStream(String clientSecret, String
private InputStream getOidcConfigurationInputStreamWithEnvironmentVariableExpression() {
String oidcConfig = "{\n" +
- " \"resource\" : \"" + CLIENT_ID + "\",\n" +
- " \"public-client\" : \"false\",\n" +
- " \"provider-url\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "${oidc.provider-url-env}\",\n" +
- " \"ssl-required\" : \"EXTERNAL\",\n" +
- " \"credentials\" : {\n" +
- " \"secret\" : \"" + CLIENT_SECRET + "\"\n" +
+ " \"" + RESOURCE + "\" : \"" + CLIENT_ID + "\",\n" +
+ " \"" + PUBLIC_CLIENT + "\" : \"false\",\n" +
+ " \"" + PROVIDER_URL + "\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "${oidc.provider-url-env}\",\n" +
+ " \"" + SSL_REQUIRED + "\" : \"EXTERNAL\",\n" +
+ " \"" + CREDENTIALS + "\" : {\n" +
+ " \"" + ClientCredentialsProviderType.SECRET.getValue() + "\" : \"" + CLIENT_SECRET + "\"\n" +
" }\n" +
"}";
return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8));
@@ -471,12 +711,12 @@ private InputStream getOidcConfigurationInputStreamWithEnvironmentVariableExpres
private InputStream getOidcConfigurationInputStreamWithSystemPropertyExpression() {
String oidcConfig = "{\n" +
- " \"resource\" : \"" + CLIENT_ID + "\",\n" +
- " \"public-client\" : \"false\",\n" +
- " \"provider-url\" : \"${oidc.provider.url}\",\n" +
- " \"ssl-required\" : \"EXTERNAL\",\n" +
- " \"credentials\" : {\n" +
- " \"secret\" : \"" + CLIENT_SECRET + "\"\n" +
+ " \"" + RESOURCE + "\" : \"" + CLIENT_ID + "\",\n" +
+ " \"" + PUBLIC_CLIENT + "\" : \"false\",\n" +
+ " \"" + PROVIDER_URL + "\" : \"${oidc.provider.url}\",\n" +
+ " \"" + SSL_REQUIRED + "\" : \"EXTERNAL\",\n" +
+ " \"" + CREDENTIALS + "\" : {\n" +
+ " \"" + ClientCredentialsProviderType.SECRET.getValue() + "\" : \"" + CLIENT_SECRET + "\"\n" +
" }\n" +
"}";
return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8));
@@ -484,12 +724,12 @@ private InputStream getOidcConfigurationInputStreamWithSystemPropertyExpression(
private InputStream getOidcConfigurationInputStreamWithProviderUrlTrailingSlash() {
String oidcConfig = "{\n" +
- " \"resource\" : \"" + CLIENT_ID + "\",\n" +
- " \"public-client\" : \"false\",\n" +
- " \"provider-url\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM + "/" + "\",\n" +
- " \"ssl-required\" : \"EXTERNAL\",\n" +
- " \"credentials\" : {\n" +
- " \"secret\" : \"" + CLIENT_SECRET + "\"\n" +
+ " \"" + RESOURCE + "\" : \"" + CLIENT_ID + "\",\n" +
+ " \"" + PUBLIC_CLIENT + "\" : \"false\",\n" +
+ " \"" + PROVIDER_URL + "\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM + "/" + "\",\n" +
+ " \"" + SSL_REQUIRED + "\" : \"EXTERNAL\",\n" +
+ " \"" + CREDENTIALS + "\" : {\n" +
+ " \"" + ClientCredentialsProviderType.SECRET.getValue() + "\" : \"" + CLIENT_SECRET + "\"\n" +
" }\n" +
"}";
return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8));
@@ -497,11 +737,11 @@ private InputStream getOidcConfigurationInputStreamWithProviderUrlTrailingSlash(
private InputStream getOidcConfigurationMissingRequiredOption() {
String oidcConfig = "{\n" +
- " \"public-client\" : \"false\",\n" +
- " \"provider-url\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM + "\",\n" +
- " \"ssl-required\" : \"EXTERNAL\",\n" +
- " \"credentials\" : {\n" +
- " \"secret\" : \"" + CLIENT_SECRET + "\"\n" +
+ " \"" + PUBLIC_CLIENT + "\" : \"false\",\n" +
+ " \"" + PROVIDER_URL + "\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM + "\",\n" +
+ " \"" + SSL_REQUIRED + "\" : \"EXTERNAL\",\n" +
+ " \"" + CREDENTIALS + "\" : {\n" +
+ " \"" + ClientCredentialsProviderType.SECRET.getValue() + "\" : \"" + CLIENT_SECRET + "\"\n" +
" }\n" +
"}";
return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8));
@@ -509,27 +749,109 @@ private InputStream getOidcConfigurationMissingRequiredOption() {
private InputStream getOidcConfigurationInputStreamWithTokenSignatureAlgorithm() {
String oidcConfig = "{\n" +
- " \"token-signature-algorithm\" : \"RS256\",\n" +
- " \"resource\" : \"" + CLIENT_ID + "\",\n" +
- " \"public-client\" : \"false\",\n" +
- " \"provider-url\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM + "\",\n" +
- " \"ssl-required\" : \"EXTERNAL\",\n" +
- " \"credentials\" : {\n" +
- " \"secret\" : \"" + CLIENT_SECRET + "\"\n" +
+ " \"" + TOKEN_SIGNATURE_ALGORITHM + "\" : \"RS256\",\n" +
+ " \"" + RESOURCE + "\" : \"" + CLIENT_ID + "\",\n" +
+ " \"" + PUBLIC_CLIENT + "\" : \"false\",\n" +
+ " \"" + PROVIDER_URL + "\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM + "\",\n" +
+ " \"" + SSL_REQUIRED + "\" : \"EXTERNAL\",\n" +
+ " \"" + CREDENTIALS + "\" : {\n" +
+ " \"" + ClientCredentialsProviderType.SECRET.getValue() + "\" : \"" + CLIENT_SECRET + "\"\n" +
+ " }\n" +
+ "}";
+ return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8));
+ }
+ private InputStream getOidcConfigurationInputStreamWithScope(String scopeValue){
+ String oidcConfig = "{\n" +
+ " \"" + Oidc.CLIENT_ID_JSON_VALUE + "\" : \"" + CLIENT_ID + "\",\n" +
+ " \"" + PROVIDER_URL + "\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM_WITH_SCOPES + "/" + "\",\n" +
+ " \"" + PUBLIC_CLIENT + "\" : \"false\",\n" +
+ " \"" + SCOPE + "\" : \"" + scopeValue + "\",\n" +
+ " \"" + SSL_REQUIRED + "\" : \"EXTERNAL\",\n" +
+ " \"" + CREDENTIALS + "\" : {\n" +
+ " \"" + ClientCredentialsProviderType.SECRET.getValue() + "\" : \"" + 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" +
+ " \"" + Oidc.CLIENT_ID_JSON_VALUE + "\" : \"" + 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" +
+ " \"" + SCOPE + "\" : \"profile email phone\",\n" +
+ " \"" + CREDENTIALS + "\" : {\n" +
+ " \"" + ClientCredentialsProviderType.SECRET.getValue() + "\" : \"" + CLIENT_SECRET + "\"\n" +
" }\n" +
"}";
return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8));
}
+ private InputStream getOidcConfigurationInputStreamWithoutEncValue(String requestParameter, String encryptionAlgorithm){
+ String oidcConfig = "{\n" +
+ " \"" + Oidc.CLIENT_ID_JSON_VALUE + "\" : \"" + 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_ENCRYPTION_ALG_VALUE + "\" : \"" + encryptionAlgorithm + "\",\n" +
+ " \"" + SCOPE + "\" : \"profile email phone\",\n" +
+ " \"" + CREDENTIALS + "\" : {\n" +
+ " \"" + ClientCredentialsProviderType.SECRET.getValue() + "\" : \"" + 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" +
+ " \"" + Oidc.CLIENT_ID_JSON_VALUE + "\" : \"" + 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" +
+ " \"" + ClientCredentialsProviderType.SECRET.getValue() + "\" : \"" + CLIENT_SECRET + "\"\n" +
+ " }\n" +
+ "}";
+ return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8));
+ }
+
+ private InputStream getOidcConfigurationInputStreamWithRequestObjectPublicClient(String requestParameter, String signingAlgorithm){
+ String oidcConfig = "{\n" +
+ " \"" + Oidc.CLIENT_ID_JSON_VALUE + "\" : \"" + 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" +
- " \"resource\" : \"" + CLIENT_ID + "\",\n" +
- " \"public-client\" : \"false\",\n" +
- " \"provider-url\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM + "\",\n" +
- " \"ssl-required\" : \"EXTERNAL\",\n" +
- " \"credentials\" : {\n" +
- " \"secret\" : \"" + CLIENT_SECRET + "\"\n" +
+ " \"" + PRINCIPAL_ATTRIBUTE + "\" : \"" + principalAttributeValue + "\",\n" +
+ " \"" + RESOURCE + "\" : \"" + CLIENT_ID + "\",\n" +
+ " \"" + PUBLIC_CLIENT + "\" : \"false\",\n" +
+ " \"" + PROVIDER_URL + "\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM + "\",\n" +
+ " \"" + SSL_REQUIRED + "\" : \"EXTERNAL\",\n" +
+ " \"" + CREDENTIALS + "\" : {\n" +
+ " \"" + ClientCredentialsProviderType.SECRET.getValue() + "\" : \"" + CLIENT_SECRET + "\"\n" +
" }\n" +
"}";
return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8));
@@ -537,13 +859,13 @@ private InputStream getOidcConfigurationInputStreamWithPrincipalAttribute(String
static InputStream getTenantConfigWithAuthServerUrl(String tenant) {
String oidcConfig = "{\n" +
- " \"realm\" : \"" + tenant + "\",\n" +
- " \"resource\" : \"" + CLIENT_ID + "\",\n" +
- " \"public-client\" : \"false\",\n" +
- " \"auth-server-url\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "\",\n" +
- " \"ssl-required\" : \"EXTERNAL\",\n" +
- " \"credentials\" : {\n" +
- " \"secret\" : \"" + CLIENT_SECRET + "\"\n" +
+ " \"" + REALM + "\" : \"" + tenant + "\",\n" +
+ " \""+ RESOURCE +"\" : \"" + CLIENT_ID + "\",\n" +
+ " \"" + PUBLIC_CLIENT +"\" : \"false\",\n" +
+ " \"" + AUTH_SERVER_URL + "\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "\",\n" +
+ " \"" + SSL_REQUIRED + "\" : \"EXTERNAL\",\n" +
+ " \"" + CREDENTIALS + "\" : {\n" +
+ " \"" + ClientCredentialsProviderType.SECRET.getValue() + "\" : \"" + CLIENT_SECRET + "\"\n" +
" }\n" +
"}";
return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8));
@@ -551,12 +873,12 @@ static InputStream getTenantConfigWithAuthServerUrl(String tenant) {
static InputStream getTenantConfigWithProviderUrl(String tenant) {
String oidcConfig = "{\n" +
- " \"provider-url\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + tenant + "\",\n" +
- " \"client-id\" : \"" + CLIENT_ID + "\",\n" +
- " \"public-client\" : \"false\",\n" +
- " \"ssl-required\" : \"EXTERNAL\",\n" +
- " \"credentials\" : {\n" +
- " \"secret\" : \"" + CLIENT_SECRET + "\"\n" +
+ " \"" + PROVIDER_URL + "\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + tenant + "\",\n" +
+ " \"" + Oidc.CLIENT_ID_JSON_VALUE + "\" : \"" + CLIENT_ID + "\",\n" +
+ " \"" + PUBLIC_CLIENT + "\" : \"false\",\n" +
+ " \"" + SSL_REQUIRED + "\" : \"EXTERNAL\",\n" +
+ " \"" + CREDENTIALS + "\" : {\n" +
+ " \"" + ClientCredentialsProviderType.SECRET.getValue() + "\" : \"" + CLIENT_SECRET + "\"\n" +
" }\n" +
"}";
return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8));
@@ -566,3 +888,4 @@ private static final String getClientPageTestForTenant(String tenant) {
return tenant.equals(TENANT1_ENDPOINT) ? TENANT1_ENDPOINT : TENANT2_ENDPOINT + ":" + CLIENT_PAGE_TEXT;
}
}
+
diff --git a/http/oidc/src/test/java/org/wildfly/security/http/oidc/QueryParamsBaseTest.java b/http/oidc/src/test/java/org/wildfly/security/http/oidc/QueryParamsBaseTest.java
index a5d7a527193..e6bb2762ed5 100644
--- a/http/oidc/src/test/java/org/wildfly/security/http/oidc/QueryParamsBaseTest.java
+++ b/http/oidc/src/test/java/org/wildfly/security/http/oidc/QueryParamsBaseTest.java
@@ -38,7 +38,7 @@ public static void startTestContainers() throws Exception {
assumeTrue("Docker isn't available, OIDC tests will be skipped", isDockerAvailable());
KEYCLOAK_CONTAINER = new KeycloakContainer();
KEYCLOAK_CONTAINER.start();
- sendRealmCreationRequest(KeycloakConfiguration.getRealmRepresentation(TEST_REALM, CLIENT_ID, CLIENT_SECRET, CLIENT_HOST_NAME, CLIENT_PORT, CLIENT_APP, 3, 3, true));
+ sendRealmCreationRequest(KeycloakConfiguration.getRealmRepresentation(TEST_REALM, CLIENT_ID, CLIENT_SECRET, CLIENT_HOST_NAME, CLIENT_PORT, CLIENT_APP, 3, 3, false, true));
client = new MockWebServer();
client.start(CLIENT_PORT);
}
diff --git a/http/oidc/src/test/java/org/wildfly/security/http/oidc/QueryParamsDisabledTest.java b/http/oidc/src/test/java/org/wildfly/security/http/oidc/QueryParamsDisabledTest.java
index e9d36b66bc6..f32771d3812 100644
--- a/http/oidc/src/test/java/org/wildfly/security/http/oidc/QueryParamsDisabledTest.java
+++ b/http/oidc/src/test/java/org/wildfly/security/http/oidc/QueryParamsDisabledTest.java
@@ -72,4 +72,3 @@ public void testSuccessfulAuthenticationWithQueryParamsWithSystemPropertyDisable
}
}
-
diff --git a/http/oidc/src/test/java/org/wildfly/security/http/oidc/QueryParamsEnabledTest.java b/http/oidc/src/test/java/org/wildfly/security/http/oidc/QueryParamsEnabledTest.java
index ff320a28e02..3f9c5515fa1 100644
--- a/http/oidc/src/test/java/org/wildfly/security/http/oidc/QueryParamsEnabledTest.java
+++ b/http/oidc/src/test/java/org/wildfly/security/http/oidc/QueryParamsEnabledTest.java
@@ -79,7 +79,13 @@ public void testSuccessfulAuthenticationWithQueryParamsWithSystemPropertyEnabled
performAuthentication(getOidcConfigurationInputStreamWithProviderUrl(), KeycloakConfiguration.ALICE,
KeycloakConfiguration.ALICE_PASSWORD, true, HttpStatus.SC_MOVED_TEMPORARILY, originalUrl,
expectedUrlAfterRedirect, CLIENT_PAGE_TEXT);
+
+ queryParams = "?url=http%3A%2F%2Flocalhost%2F%3Fone%3Dabc%26two%3Ddef&three=ghi";
+ originalUrl = getClientUrl() + queryParams;
+ expectedUrlAfterRedirect = originalUrl;
+ performAuthentication(getOidcConfigurationInputStreamWithProviderUrl(), KeycloakConfiguration.ALICE,
+ KeycloakConfiguration.ALICE_PASSWORD, true, HttpStatus.SC_MOVED_TEMPORARILY, originalUrl,
+ expectedUrlAfterRedirect, CLIENT_PAGE_TEXT);
}
}
-
diff --git a/http/spnego/pom.xml b/http/spnego/pom.xml
index 652384b9a15..f44833a344d 100644
--- a/http/spnego/pom.xml
+++ b/http/spnego/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/http/sso/pom.xml b/http/sso/pom.xml
index 1e6e3a56441..3db2fc06dc4 100644
--- a/http/sso/pom.xml
+++ b/http/sso/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/http/sso/src/main/java/org/wildfly/security/http/util/sso/SingleSignOnServerMechanismFactory.java b/http/sso/src/main/java/org/wildfly/security/http/util/sso/SingleSignOnServerMechanismFactory.java
index 867560e90eb..17dc3309357 100644
--- a/http/sso/src/main/java/org/wildfly/security/http/util/sso/SingleSignOnServerMechanismFactory.java
+++ b/http/sso/src/main/java/org/wildfly/security/http/util/sso/SingleSignOnServerMechanismFactory.java
@@ -46,6 +46,7 @@
* The single sign-one capabilities provided by this factory is based on a HTTP Cookie to track SSO sessions and also an {@link IdentityCache} providing
* a storage (eg.: using a shared or distributable cache/map) for these sessions and related data.
*
+ * @deprecated Only inner class SingleSignOnConfiguration is deprecated.
* @author Pedro Igor
* @author Paul Ferraro
*/
diff --git a/http/stateful-basic/pom.xml b/http/stateful-basic/pom.xml
index 7e240807980..ff8fd8a810b 100644
--- a/http/stateful-basic/pom.xml
+++ b/http/stateful-basic/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/http/stateful-basic/src/main/java/org/wildfly/security/http/sfbasic/BasicAuthenticationMechanism.java b/http/stateful-basic/src/main/java/org/wildfly/security/http/sfbasic/BasicAuthenticationMechanism.java
index 103bddbaedf..eccff5eb0e5 100644
--- a/http/stateful-basic/src/main/java/org/wildfly/security/http/sfbasic/BasicAuthenticationMechanism.java
+++ b/http/stateful-basic/src/main/java/org/wildfly/security/http/sfbasic/BasicAuthenticationMechanism.java
@@ -329,48 +329,7 @@ public CachedIdentity remove() {
}
private static HttpServerCookie createCookie(final String name, final String value) {
- return new HttpServerCookie() {
-
- @Override
- public boolean isSecure() {
- return false;
- }
-
- @Override
- public boolean isHttpOnly() {
- return false;
- }
-
- @Override
- public int getVersion() {
- return 0;
- }
-
- @Override
- public String getValue() {
- return value;
- }
-
- @Override
- public String getPath() {
- return "/";
- }
-
- @Override
- public String getName() {
- return name;
- }
-
- @Override
- public int getMaxAge() {
- return -1;
- }
-
- @Override
- public String getDomain() {
- return null;
- }
- };
+ return HttpServerCookie.getInstance(name, value, null, -1, "/", false, 0, false);
}
}
diff --git a/http/util/pom.xml b/http/util/pom.xml
index 41b5708936c..3643831549e 100644
--- a/http/util/pom.xml
+++ b/http/util/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/jose/jwk/pom.xml b/jose/jwk/pom.xml
index 286fcbc9d2f..4a82a6a3894 100644
--- a/jose/jwk/pom.xml
+++ b/jose/jwk/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/jose/util/pom.xml b/jose/util/pom.xml
index 0b5931947eb..ce6101c1ffb 100644
--- a/jose/util/pom.xml
+++ b/jose/util/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/json-util/pom.xml b/json-util/pom.xml
index 52c14cae984..6ecab3a776c 100644
--- a/json-util/pom.xml
+++ b/json-util/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
4.0.0
diff --git a/keystore/pom.xml b/keystore/pom.xml
index 7d719a494c6..4ab364a873b 100644
--- a/keystore/pom.xml
+++ b/keystore/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
4.0.0
diff --git a/keystore/src/test/java/org/wildfly/security/keystore/KeyStoreUtilTest.java b/keystore/src/test/java/org/wildfly/security/keystore/KeyStoreUtilTest.java
index 1ff8b92dbaa..07bc7814a81 100644
--- a/keystore/src/test/java/org/wildfly/security/keystore/KeyStoreUtilTest.java
+++ b/keystore/src/test/java/org/wildfly/security/keystore/KeyStoreUtilTest.java
@@ -102,122 +102,32 @@ public void afterTest() {
@Test
public void testJKS() throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException {
- System.out.println("Testing JKS...");
- Certificate jkscert = generateCertificate();
- String filename = "testks.jks";
- String alias = "alias";
- char[] password = "password".toCharArray();
-
- generateKeyStoreWithKey(filename, "jks", alias, password, jkscert);
-
- KeyStore loadedStore = KeyStoreUtil.loadKeyStore(providerSupplier, null, new FileInputStream(new File(workingDir, filename)), filename, password);
- Assert.assertNotNull(loadedStore);
- Certificate loadedCert = loadedStore.getCertificate(alias);
-
- Assert.assertEquals(jkscert, loadedCert);
+ testKeyStore("testks.jks", "jks", false);
}
@Test
public void testJCEKS() throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException {
- System.out.println("Testing JCEKS...");
- Certificate jkscert = generateCertificate();
- String filename = "testks.pkcs12";
- String alias = "alias";
- char[] password = "password".toCharArray();
-
- generateKeyStoreWithKey(filename, "jceks", alias, password, jkscert);
-
- KeyStore loadedStore = KeyStoreUtil.loadKeyStore(providerSupplier, null, new FileInputStream(new File(workingDir, filename)), filename, password);
- Assert.assertNotNull(loadedStore);
- Certificate loadedCert = loadedStore.getCertificate(alias);
-
- Assert.assertEquals(jkscert, loadedCert);
+ testKeyStore("testks.pkcs12", "jceks", false);
}
@Test
public void testPKCS12() throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException {
- System.out.println("Testing PKCS12...");
- Certificate jkscert = generateCertificate();
- String filename = "testks.asdf";
- String alias = "alias";
- char[] password = "password".toCharArray();
-
- generateKeyStoreWithKey(filename, "pkcs12", alias, password, jkscert);
-
- KeyStore loadedStore = KeyStoreUtil.loadKeyStore(providerSupplier, null, new FileInputStream(new File(workingDir, filename)), filename, password);
- Assert.assertNotNull(loadedStore);
- Certificate loadedCert = loadedStore.getCertificate(alias);
-
- Assert.assertEquals(jkscert, loadedCert);
+ testKeyStore("testks.asdf", "pkcs12", false);
}
@Test
- public void testBKS() throws CertificateException, KeyStoreException, IOException {
- System.out.println("Testing BKS...");
- Certificate jkscert = generateCertificate();
- String filename = "testks.bks";
- String alias = "alias";
- char[] password = "password".toCharArray();
- boolean bcfailed = false;
- try {
- generateKeyStoreWithKey(filename, "bks", alias, password, jkscert);
- } catch (Exception e) {
- bcfailed = true;
- }
-
- Assume.assumeFalse("BC elytronProvider not found, skipping BC keystore recognition", bcfailed);
-
- KeyStore loadedStore = KeyStoreUtil.loadKeyStore(providerSupplier, null, new FileInputStream(new File(workingDir, filename)), filename, password);
- Assert.assertNotNull(loadedStore);
- Certificate loadedCert = loadedStore.getCertificate(alias);
-
- Assert.assertEquals(jkscert, loadedCert);
+ public void testBKS() throws CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException {
+ testKeyStore("testks.asdf", "bks", true);
}
@Test
- public void testUBER() throws CertificateException, KeyStoreException, IOException {
- System.out.println("Testing UBER...");
- Certificate jkscert = generateCertificate();
- String filename = "testks.ubr";
- String alias = "alias";
- char[] password = "password".toCharArray();
- boolean bcfailed = false;
- try {
- generateKeyStoreWithKey(filename, "uber", alias, password, jkscert);
- } catch (Exception e) {
- bcfailed = true;
- }
-
- Assume.assumeFalse("BC elytronProvider not found, skipping BC keystore recognition", bcfailed);
-
- KeyStore loadedStore = KeyStoreUtil.loadKeyStore(providerSupplier, null, new FileInputStream(new File(workingDir, filename)), filename, password);
- Assert.assertNotNull(loadedStore);
- Certificate loadedCert = loadedStore.getCertificate(alias);
-
- Assert.assertEquals(jkscert, loadedCert);
+ public void testUBER() throws CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException {
+ testKeyStore("testks.asdf", "uber", true);
}
@Test
- public void testBCFKS() throws CertificateException, KeyStoreException, IOException {
- System.out.println("Testing BCFKS...");
- Certificate jkscert = generateCertificate();
- String filename = "testks.bcfks";
- String alias = "alias";
- char[] password = "password".toCharArray();
- boolean bcfailed = false;
- try {
- generateKeyStoreWithKey(filename, "bcfks", alias, password, jkscert);
- } catch (Exception e) {
- bcfailed = true;
- }
-
- Assume.assumeFalse("BC elytronProvider not found, skipping BC keystore recognition", bcfailed);
-
- KeyStore loadedStore = KeyStoreUtil.loadKeyStore(providerSupplier, null, new FileInputStream(new File(workingDir, filename)), filename, password);
- Assert.assertNotNull(loadedStore);
- Certificate loadedCert = loadedStore.getCertificate(alias);
-
- Assert.assertEquals(jkscert, loadedCert);
+ public void testBCFKS() throws CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException {
+ testKeyStore("testks.asdf", "bcfks", true);
}
@Test
@@ -302,4 +212,29 @@ private static File getWorkingDir() {
}
return workingDir;
}
+
+ private void testKeyStore(String filename, String keystoreType, boolean testBCKeyStore) throws CertificateException, KeyStoreException, NoSuchAlgorithmException, IOException {
+ System.out.println("Testing " + keystoreType.toUpperCase() + "...");
+ Certificate jkscert = generateCertificate();
+ String alias = "alias";
+ char[] password = "password".toCharArray();
+
+ if (testBCKeyStore) {
+ boolean bcfailed = false;
+ try {
+ generateKeyStoreWithKey(filename, keystoreType, alias, password, jkscert);
+ } catch (Exception e) {
+ bcfailed = true;
+ }
+ Assume.assumeFalse("BC elytronProvider not found, skipping BC keystore recognition", bcfailed);
+ } else {
+ generateKeyStoreWithKey(filename, keystoreType, alias, password, jkscert);
+ }
+
+ KeyStore loadedStore = KeyStoreUtil.loadKeyStore(providerSupplier, null, new FileInputStream(new File(workingDir, filename)), filename, password);
+ Assert.assertNotNull(loadedStore);
+ Certificate loadedCert = loadedStore.getCertificate(alias);
+
+ Assert.assertEquals(jkscert, loadedCert);
+ }
}
diff --git a/manager/action/pom.xml b/manager/action/pom.xml
index bc2e0010dea..b0b5cc6e8bd 100644
--- a/manager/action/pom.xml
+++ b/manager/action/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/manager/base/pom.xml b/manager/base/pom.xml
index 5b38909c483..c43c374f491 100644
--- a/manager/base/pom.xml
+++ b/manager/base/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/manager/base/src/test/java/org/wildfly/security/manager/AlternateSecurityManagerTest.java b/manager/base/src/test/java/org/wildfly/security/manager/AlternateSecurityManagerTest.java
index c12f0d51329..c873a635f72 100644
--- a/manager/base/src/test/java/org/wildfly/security/manager/AlternateSecurityManagerTest.java
+++ b/manager/base/src/test/java/org/wildfly/security/manager/AlternateSecurityManagerTest.java
@@ -32,6 +32,7 @@
import java.util.Stack;
import org.junit.After;
+import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.wildfly.security.ParametricPrivilegedAction;
@@ -84,6 +85,8 @@ public class AlternateSecurityManagerTest {
@Before
public void before() {
+ Assume.assumeTrue("Skipping AlternateSecurityManagerTest suite, tests are not being run on JDK 17 or lower.",
+ Integer.parseInt(System.getProperty("java.specification.version")) <= 17);
AccessControlContext current = AccessController.getContext();
ProtectionDomain[] domains = getProtectionDomainStack(current);
diff --git a/mechanism/base/pom.xml b/mechanism/base/pom.xml
index 107e155f66b..e8b21d9cb63 100644
--- a/mechanism/base/pom.xml
+++ b/mechanism/base/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/mechanism/base/src/main/java/org/wildfly/security/mechanism/AuthenticationMechanismException.java b/mechanism/base/src/main/java/org/wildfly/security/mechanism/AuthenticationMechanismException.java
index 525fd9fdba3..e5a82cb2d03 100644
--- a/mechanism/base/src/main/java/org/wildfly/security/mechanism/AuthenticationMechanismException.java
+++ b/mechanism/base/src/main/java/org/wildfly/security/mechanism/AuthenticationMechanismException.java
@@ -102,6 +102,14 @@ public static AuthenticationMechanismException fromException(final Exception sou
return copyContents(source, new AuthenticationMechanismException(source.getMessage(), source.getCause()));
}
+ /**
+ * Copies the stack trace and suppressed exceptions from a source exception to a specified throwable.
+ *
+ * @param source the source exception from which the stack trace and suppressed exceptions should be copied.
+ * @param throwable the throwable to which the contents should be copied.
+ * @param the type of throwable to which the contents should be copied.
+ * @return the throwable that was passed in as a parameter, with the contents copied from the source exception.
+ */
private static T copyContents(final Exception source, final T throwable) {
throwable.setStackTrace(source.getStackTrace());
final Throwable[] suppressed = source.getSuppressed();
diff --git a/mechanism/base/src/main/java/org/wildfly/security/mechanism/MechanismUtil.java b/mechanism/base/src/main/java/org/wildfly/security/mechanism/MechanismUtil.java
index 622a53c1f1b..28243d661b5 100644
--- a/mechanism/base/src/main/java/org/wildfly/security/mechanism/MechanismUtil.java
+++ b/mechanism/base/src/main/java/org/wildfly/security/mechanism/MechanismUtil.java
@@ -37,7 +37,7 @@
*
* @author David M. Lloyd
*
- * @deprecated Should not be part of public API. Moved into internal {@link org.wildfly.security.mechanism._private.MechanismUtil}.
+ * @deprecated Should not be part of public API. Moved into internal {@link org.wildfly.security.mechanism._private.MechanismUtil org.wildfly.security.mechanism._private.MechanismUtil}.
*/
@Deprecated
public final class MechanismUtil {
@@ -57,6 +57,7 @@ private MechanismUtil() {}
* @param providers the security providers to use with the {@link PasswordFactory}
* @param the password type
* @return the password
+ * @throws AuthenticationMechanismException if there is an error retrieving the password
*/
@Deprecated
public static S getPasswordCredential(String userName, CallbackHandler callbackHandler, Class passwordType, String passwordAlgorithm, AlgorithmParameterSpec matchParameters, AlgorithmParameterSpec generateParameters, Supplier providers) throws AuthenticationMechanismException {
@@ -78,6 +79,7 @@ public static S getPasswordCredential(String userName, Call
* @param the password type
* @param log mechanism specific logger
* @return the password
+ * @throws AuthenticationMechanismException if there is an error retrieving the password
*/
@Deprecated
public static S getPasswordCredential(String userName, CallbackHandler callbackHandler, Class passwordType, String passwordAlgorithm, AlgorithmParameterSpec matchParameters, AlgorithmParameterSpec generateParameters, Supplier providers, ElytronMessages log) throws AuthenticationMechanismException {
diff --git a/mechanism/base/src/main/java/org/wildfly/security/mechanism/ScramServerErrorCode.java b/mechanism/base/src/main/java/org/wildfly/security/mechanism/ScramServerErrorCode.java
index f1659f2dcef..111a4c2a340 100644
--- a/mechanism/base/src/main/java/org/wildfly/security/mechanism/ScramServerErrorCode.java
+++ b/mechanism/base/src/main/java/org/wildfly/security/mechanism/ScramServerErrorCode.java
@@ -45,6 +45,9 @@ public enum ScramServerErrorCode {
private final String text;
private final byte[] messageBytes;
+ /**
+ * Creates an error code instance with a String representation and a byte array for error message.
+ */
ScramServerErrorCode() {
text = name().replace('_', '-').toLowerCase(Locale.US);
final int length = text.length();
@@ -54,14 +57,29 @@ public enum ScramServerErrorCode {
messageBytes = msg;
}
+ /**
+ * Returns the String representation of the error code.
+ *
+ * @return String representation of the error code.
+ */
public String getText() {
return text;
}
+ /**
+ * Returns the copy of the byte array representing the error message.
+ *
+ * @return copy of the byte array representing the error message.
+ */
public byte[] getMessageBytes() {
return messageBytes.clone();
}
+ /**
+ * Returns the byte array representing the error message.
+ *
+ * @return the byte array representing the error message.
+ */
byte[] getRawMessageBytes() {
return messageBytes;
}
diff --git a/mechanism/base/src/main/java/org/wildfly/security/mechanism/ScramServerException.java b/mechanism/base/src/main/java/org/wildfly/security/mechanism/ScramServerException.java
index a0594f69c42..7e7057ad1d2 100644
--- a/mechanism/base/src/main/java/org/wildfly/security/mechanism/ScramServerException.java
+++ b/mechanism/base/src/main/java/org/wildfly/security/mechanism/ScramServerException.java
@@ -83,6 +83,11 @@ public String getMessage() {
return super.getMessage() + ": " + error.getText();
}
+ /**
+ * Returns the error code of the exception.
+ *
+ * @return the error code of the exception.
+ */
public ScramServerErrorCode getError() {
return error;
}
diff --git a/mechanism/base/src/main/java/org/wildfly/security/mechanism/_private/MechanismUtil.java b/mechanism/base/src/main/java/org/wildfly/security/mechanism/_private/MechanismUtil.java
index 74e7b99e85c..32192d27f85 100644
--- a/mechanism/base/src/main/java/org/wildfly/security/mechanism/_private/MechanismUtil.java
+++ b/mechanism/base/src/main/java/org/wildfly/security/mechanism/_private/MechanismUtil.java
@@ -67,6 +67,7 @@ private MechanismUtil() {}
* @param the password type
* @param log mechanism specific logger
* @return the password
+ * @throws AuthenticationMechanismException if there is an error retrieving the password
*/
public static S getPasswordCredential(String userName, CallbackHandler callbackHandler, Class passwordType, String passwordAlgorithm, AlgorithmParameterSpec matchParameters, AlgorithmParameterSpec generateParameters, Supplier providers, ElytronMessages log) throws AuthenticationMechanismException {
Assert.checkNotNullParam("userName", userName);
@@ -168,6 +169,7 @@ public static void handleCallbacks(ElytronMessages log, CallbackHandler callback
* @param scope the HTTP scope to store computed value (must not be {@code null})
* @param key the key to retrieve (must not be {@code null})
* @param mappingFunction the function to apply to acquire the value (must not be {@code null})
+ * @param the type of returned value
* @return the stored or new value (not {@code null})
*/
public static R computeIfAbsent(HttpScope scope, String key, Function mappingFunction) {
diff --git a/mechanism/digest/pom.xml b/mechanism/digest/pom.xml
index 0b85d633b40..c9989e73abb 100644
--- a/mechanism/digest/pom.xml
+++ b/mechanism/digest/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/mechanism/digest/src/main/java/org/wildfly/security/mechanism/digest/DigestQuote.java b/mechanism/digest/src/main/java/org/wildfly/security/mechanism/digest/DigestQuote.java
index 0f6cc400572..da81adc1fb7 100644
--- a/mechanism/digest/src/main/java/org/wildfly/security/mechanism/digest/DigestQuote.java
+++ b/mechanism/digest/src/main/java/org/wildfly/security/mechanism/digest/DigestQuote.java
@@ -21,7 +21,7 @@
import org.wildfly.common.bytes.ByteStringBuilder;
/**
- * Utility class used to convert string to quoted strings
+ * Utility class used to convert string to quoted strings.
*
* @author Peter Skopek
*
@@ -33,6 +33,12 @@ public class DigestQuote {
private DigestQuote() {
}
+ /**
+ * Checks if a given character needs to be quoted.
+ *
+ * @param ch the character to check.
+ * @return {@code true} if the character needs to be quoted, {@code false} otherwise.
+ */
private static boolean quoteNeeded(char ch) {
return
ch == '"' || // escape char
@@ -46,8 +52,8 @@ private static boolean quoteNeeded(char ch) {
/**
* Creates new String quoted by SASL rules.
*
- * @param inputStr String to be quoted
- * @return
+ * @param inputStr String to be quoted.
+ * @return new String with quoted characters.
*/
public static String quote(String inputStr) {
int len = inputStr.length();
@@ -64,6 +70,12 @@ public static String quote(String inputStr) {
return sb.toString();
}
+ /**
+ * Creates new Array quoted by SASL rules.
+ *
+ * @param input Byte array to be quoted.
+ * @return new byte array with quoted bytes.
+ */
public static byte[] quote(byte[] input) {
ByteStringBuilder bsb = new ByteStringBuilder();
for (int i = 0; i < input.length; i++) {
diff --git a/mechanism/digest/src/main/java/org/wildfly/security/mechanism/digest/DigestUtil.java b/mechanism/digest/src/main/java/org/wildfly/security/mechanism/digest/DigestUtil.java
index fc4e4399638..c277abd51d1 100644
--- a/mechanism/digest/src/main/java/org/wildfly/security/mechanism/digest/DigestUtil.java
+++ b/mechanism/digest/src/main/java/org/wildfly/security/mechanism/digest/DigestUtil.java
@@ -30,8 +30,6 @@
import java.util.HashMap;
import java.util.function.Supplier;
-import javax.security.sasl.SaslException;
-
import org.wildfly.common.bytes.ByteStringBuilder;
import org.wildfly.security.mechanism._private.ElytronMessages;
import org.wildfly.security.mechanism.AuthenticationMechanismException;
@@ -52,9 +50,12 @@ public class DigestUtil {
/**
* Client side method to parse challenge sent by server.
*
- * @param challenge
- * @return
- * @throws AuthenticationMechanismException
+ * @param challenge the byte array representing the authentication challenge to be parsed.
+ * @param charset the charset to decide which whitespace separator is used.
+ * @param multiRealm {@code true} if there are multiple realms in the challenge, {@code false} otherwise
+ * @param log the logger to use.
+ * @return A new HashMap representing response for the parsed challenge
+ * @throws AuthenticationMechanismException if there is an error parsing the challenge
*/
public static HashMap parseResponse(byte [] challenge, Charset charset, boolean multiRealm, ElytronMessages log) throws AuthenticationMechanismException {
@@ -170,6 +171,15 @@ else if (expectSeparator) {
return response;
}
+ /**
+ * Adds a key-value pair to a HashMap representing a parsed challenge.
+ *
+ * @param response the HashMap to add the key-value pair to.
+ * @param keyBuilder the StringBuilder containing the key.
+ * @param valueBuilder the ByteStringBuilder containing the value.
+ * @param realmNumber the current number of realms in the parsed challenge.
+ * @return the updated number of realms in the parsed challenge.
+ */
private static int addToParsedChallenge(HashMap response, StringBuilder keyBuilder, ByteStringBuilder valueBuilder, int realmNumber) {
String k = keyBuilder.toString();
byte[] v = valueBuilder.toArray();
@@ -183,6 +193,13 @@ private static int addToParsedChallenge(HashMap response, String
return realmNumber;
}
+ /**
+ * Finds the next non-whitespace character in a byte array.
+ *
+ * @param buffer the byte array to search in.
+ * @param startPoint the starting point in the buffer to begin the search.
+ * @return the index of the next non-whitespace character.
+ */
private static int skipWhiteSpace(byte[] buffer, int startPoint) {
int i = startPoint;
while (i < buffer.length && isWhiteSpace(buffer[i])) {
@@ -191,6 +208,12 @@ private static int skipWhiteSpace(byte[] buffer, int startPoint) {
return i;
}
+ /**
+ * Checks if a given byte is a whitespace character.
+ *
+ * @param b the byte to check.
+ * @return {@code true} if the byte is a whitespace character, {@code false} otherwise.
+ */
private static boolean isWhiteSpace(byte b) {
if (b == 13) // CR
return true;
@@ -204,6 +227,15 @@ else if (b == 32) // SPACE
return false;
}
+ /**
+ * Digests the concatenated username, realm and password.
+ *
+ * @param messageDigest the message digest algorithm to use when computing the digest.
+ * @param username the username to use when concatenating.
+ * @param realm the realm to use when concatenating.
+ * @param password the password in the form of a char array to use when concatenating.
+ * @return byte array of the digested password.
+ */
public static byte[] userRealmPasswordDigest(MessageDigest messageDigest, String username, String realm, char[] password) {
CharsetEncoder latin1Encoder = StandardCharsets.ISO_8859_1.newEncoder();
latin1Encoder.reset();
@@ -232,10 +264,13 @@ public static byte[] userRealmPasswordDigest(MessageDigest messageDigest, String
}
/**
- * Get array of password chars from TwoWayPassword
+ * Get array of password chars from TwoWayPassword.
*
- * @return
- * @throws SaslException
+ * @param password the TwoWayPassword that needs to be processed.
+ * @param providers the supplier for the providers to be used for processing.
+ * @param log the logger to use.
+ * @throws AuthenticationMechanismException if there is an error retrieving the encoded password.
+ * @return encoded password in the form of a char array.
*/
public static char[] getTwoWayPasswordChars(TwoWayPassword password, Supplier providers, ElytronMessages log) throws AuthenticationMechanismException {
if (password == null) {
diff --git a/mechanism/digest/src/main/java/org/wildfly/security/mechanism/digest/PasswordDigestObtainer.java b/mechanism/digest/src/main/java/org/wildfly/security/mechanism/digest/PasswordDigestObtainer.java
index b876af7ad2c..7af037bd6ba 100644
--- a/mechanism/digest/src/main/java/org/wildfly/security/mechanism/digest/PasswordDigestObtainer.java
+++ b/mechanism/digest/src/main/java/org/wildfly/security/mechanism/digest/PasswordDigestObtainer.java
@@ -45,7 +45,7 @@
import static org.wildfly.security.mechanism.digest.DigestUtil.userRealmPasswordDigest;
/**
- * Utility class used to obtain username+realm+password using SASL/HTTP mechanism callbacks
+ * Utility class used to obtain username+realm+password using SASL/HTTP mechanism callbacks.
*
* @author Jan Kalina
*/
@@ -67,6 +67,20 @@ public class PasswordDigestObtainer {
private RealmCallback realmCallback;
private NameCallback nameCallback;
+ /**
+ * Constructs a new {@code PasswordDigestObtainer} instance.
+ *
+ * @param callbackHandler the callbackHandler to handle the callbacks required to obtain the username and password.
+ * @param defaultUsername the default username to use if a callback is not provided.
+ * @param defaultRealm the default realm to use if a callback is not provided.
+ * @param log the logger to use.
+ * @param credentialAlgorithm the name of the algorithm for obtaining the credential.
+ * @param messageDigest the {@link MessageDigest} used for digesting the password.
+ * @param passwordFactoryProviders the supplier of the providers to use when creating a {@code PasswordFactory} instance.
+ * @param realms the realms to check for a user and password.
+ * @param readOnlyRealmUsername {@code true} if the username passed in the callback can be modified, {@code false} otherwise.
+ * @param skipRealmCallbacks {@code true} if realm callbacks should be skipped, {@code false} otherwise.
+ */
public PasswordDigestObtainer(CallbackHandler callbackHandler, String defaultUsername, String defaultRealm,
ElytronMessages log, String credentialAlgorithm, MessageDigest messageDigest,
Supplier passwordFactoryProviders, String[] realms,
@@ -83,14 +97,30 @@ public PasswordDigestObtainer(CallbackHandler callbackHandler, String defaultUse
this.skipRealmCallbacks = skipRealmCallbacks;
}
+ /**
+ * Returns the username obtained from callback or the default one.
+ *
+ * @return the username obtained from callback or the default one.
+ */
public String getUsername() {
return username;
}
+ /**
+ * Returns the realm obtained from callback or the default one.
+ *
+ * @return the realm obtained from callback or the default one.
+ */
public String getRealm() {
return realm;
}
+ /**
+ * Handles callbacks for user and password information.
+ *
+ * @return the salted password.
+ * @throws AuthenticationMechanismException if the callback handler does not support credential acquisition.
+ */
public byte[] handleUserRealmPasswordCallbacks() throws AuthenticationMechanismException {
realmChoiceCallBack = skipRealmCallbacks || realms == null || realms.length <= 1 ? null :
@@ -115,6 +145,12 @@ public byte[] handleUserRealmPasswordCallbacks() throws AuthenticationMechanismE
throw log.mechCallbackHandlerDoesNotSupportCredentialAcquisition(null);
}
+ /**
+ * Obtains the pre-digested salted password for the {@code username} in the {@code realm}.
+ *
+ * @return the pre-digested salted password if obtained, {@code null} otherwise.
+ * @throws AuthenticationMechanismException if an exception occurs while handling the callbacks.
+ */
private byte[] getPredigestedSaltedPassword() throws AuthenticationMechanismException {
if (realmChoiceCallBack != null) {
try {
@@ -180,6 +216,12 @@ private byte[] getPredigestedSaltedPassword() throws AuthenticationMechanismExce
return null;
}
+ /**
+ * Obtains the salted password from a two-way callback.
+ *
+ * @return the byte array of the salted password if obtained, {@code null} otherwise.
+ * @throws AuthenticationMechanismException if an error occurs during the process of handling callbacks or obtaining the password.
+ */
private byte[] getSaltedPasswordFromTwoWay() throws AuthenticationMechanismException {
if (realmChoiceCallBack != null) {
try {
@@ -253,6 +295,12 @@ private byte[] getSaltedPasswordFromTwoWay() throws AuthenticationMechanismExcep
return null;
}
+ /**
+ * Obtains the salted password from a password callback.
+ *
+ * @return the byte array of the salted password.
+ * @throws AuthenticationMechanismException if an error occurs during the process of handling callbacks or obtaining the password.
+ */
private byte[] getSaltedPasswordFromPasswordCallback() throws AuthenticationMechanismException {
PasswordCallback passwordCallback = new PasswordCallback("User password: ", false);
diff --git a/mechanism/gssapi/pom.xml b/mechanism/gssapi/pom.xml
index 055553ef208..adccd6cda1e 100644
--- a/mechanism/gssapi/pom.xml
+++ b/mechanism/gssapi/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/mechanism/gssapi/src/main/java/org/wildfly/security/mechanism/gssapi/GSSCredentialSecurityFactory.java b/mechanism/gssapi/src/main/java/org/wildfly/security/mechanism/gssapi/GSSCredentialSecurityFactory.java
index 03566163be3..2322b9ede25 100644
--- a/mechanism/gssapi/src/main/java/org/wildfly/security/mechanism/gssapi/GSSCredentialSecurityFactory.java
+++ b/mechanism/gssapi/src/main/java/org/wildfly/security/mechanism/gssapi/GSSCredentialSecurityFactory.java
@@ -87,12 +87,25 @@ public final class GSSCredentialSecurityFactory implements SecurityFactory credentialOperator;
+ /**
+ * Constructs a new {@code GSSCredentialSecurityFactory} instance.
+ *
+ * @param minimumRemainingLifetime the minimum remaining lifetime for a {@link GSSCredential} in seconds.
+ * @param rawSupplier the supplier of raw credentials.
+ */
GSSCredentialSecurityFactory(final int minimumRemainingLifetime, final ExceptionSupplier rawSupplier) {
this.minimumRemainingLifetime = minimumRemainingLifetime;
this.rawSupplier = rawSupplier;
credentialOperator = this::update;
}
+ /**
+ * Updates the {@link GSSKerberosCredential}. If the original is not valid, it gets a new {@code GSSKerberosCredential}
+ * from the {@code rawSupplier}, otherwise returns the original.
+ *
+ * @param original the original {@code GSSKerberosCredential} to be updated.
+ * @return the original if still valid, new {@code GSSKerberosCredential} otherwise.
+ */
private GSSKerberosCredential update(GSSKerberosCredential original) {
GSSKerberosCredential result = null;
try {
@@ -116,6 +129,13 @@ private GSSKerberosCredential update(GSSKerberosCredential original) {
return result;
}
+ /**
+ * Checks if the GSSCredential is still valid.
+ *
+ * @param gssCredential the GSSCredential to check.
+ * @return {@code true} if the GSSCredential is valid, {@code false} otherwise.
+ * @throws GeneralSecurityException if an error occurs during the validation.
+ */
private boolean testIsValid(GSSCredential gssCredential) throws GeneralSecurityException {
checkNotNullParam("gssCredential", gssCredential);
boolean stillValid;
@@ -131,6 +151,12 @@ private boolean testIsValid(GSSCredential gssCredential) throws GeneralSecurityE
return stillValid;
}
+ /**
+ * Checks if the Kerberos ticket is still valid. If not, attempts to refresh it.
+ *
+ * @param ticket the Kerberos ticket to be checked.
+ * @return {@code true} if the ticket is valid, {@code false} otherwise.
+ */
private boolean testIsValid(KerberosTicket ticket) {
if (ticket == null) {
log.trace("No cached KerberosTicket");
@@ -231,9 +257,9 @@ public Builder setIsServer(final boolean isServer) {
}
/**
- * Set if the KerberosTicket should also be obtained and associated with the Credential/
+ * Set if the KerberosTicket should also be obtained and associated with the Credential.
*
- * @param obtainKerberosTicket if the KerberosTicket should also be obtained and associated with the Credential/
+ * @param obtainKerberosTicket if the KerberosTicket should also be obtained and associated with the Credential.
* @return {@code this} to allow chaining.
*/
public Builder setObtainKerberosTicket(final boolean obtainKerberosTicket) {
@@ -297,7 +323,7 @@ public Builder setPrincipal(final String principal) {
}
/**
- * Set if debug logging should be enabled for the JAAS authentication portion of obtaining the {@link GSSCredential}
+ * Set if debug logging should be enabled for the JAAS authentication portion of obtaining the {@link GSSCredential}.
*
* @param debug if debug logging should be enabled for the JAAS authentication portion of obtaining the {@link GSSCredential}
* @return {@code this} to allow chaining.
@@ -336,7 +362,7 @@ public Builder setCheckKeyTab(final boolean value) {
}
/**
- * Set other configuration options for {@code Krb5LoginModule}
+ * Set other configuration options for {@code Krb5LoginModule}.
*
* @param options the configuration options which will be appended to options passed into {@code Krb5LoginModule}
* @return {@code this} to allow chaining.
@@ -380,6 +406,14 @@ public SecurityFactory build() throws IOException {
return new GSSCredentialSecurityFactory(minimumRemainingLifetime > 0 ? minimumRemainingLifetime : 0, () -> createGSSCredential(configuration));
}
+ /**
+ * Creates an instance of the {@link GSSKerberosCredential} class, which represents a Kerberos credential
+ * that can be used for authentication using the GSS-API.
+ *
+ * @param configuration the configuration used for creating the {@link LoginContext}.
+ * @return the {@code GSSKerberosCredential} - the GSSCredential object and Kerberos Ticket (if {@code obtainKerberosTicket} is {@code true}.
+ * @throws GeneralSecurityException if an error occurs during the creation of {@code GSSKerberosCredential}.
+ */
private GSSKerberosCredential createGSSCredential(Configuration configuration) throws GeneralSecurityException {
if (failCache != 0 && System.currentTimeMillis() - lastFailTime < failCache * 1000) {
throw log.initialLoginSkipped(failCache);
@@ -445,10 +479,24 @@ private GSSKerberosCredential createGSSCredential(Configuration configuration) t
}
}
+ /**
+ * Performs a privileged action. If a security manager is set, the action will be executed via
+ * {@link AccessController#doPrivileged(PrivilegedAction)}. If no security manager is set,
+ * the action will be executed directly.
+ *
+ * @param action the action do be executed.
+ * @param the type of the action.
+ * @return the result of the executed action.
+ */
private static T doPrivileged(final PrivilegedAction action) {
return System.getSecurityManager() != null ? AccessController.doPrivileged(action) : action.run();
}
+ /**
+ * Checks if the keytab exists and if it contains any keys for the specified principal.
+ *
+ * @throws IOException if the keytab does not exist or if it does not contain any keys for the specified principal.
+ */
private void checkKeyTab() throws IOException {
KeyTab kt = KeyTab.getInstance(keyTab);
if (!kt.exists()) {
@@ -459,6 +507,12 @@ private void checkKeyTab() throws IOException {
}
}
+ /**
+ * Creates a {@link Configuration} that is used to initiate a {@link LoginContext}.
+ *
+ * @return a {@code Configuration} for initiating a {@code LoginContext}.
+ * @throws IOException if the keyTab does not exist or there are no keys for the principal in the keyTab.
+ */
private Configuration createConfiguration() throws IOException {
Map options = new HashMap<>();
if (debug) {
@@ -491,6 +545,9 @@ public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
};
}
+ /**
+ * Asserts that the builder has not yet been built.
+ */
private void assertNotBuilt() {
if (built) {
throw log.builderAlreadyBuilt();
@@ -499,6 +556,12 @@ private void assertNotBuilt() {
}
+ /**
+ * Wraps the given {@link GSSCredential} and prevents it from being disposed.
+ *
+ * @param credential the {@code GSSCredential} to be wrapped.
+ * @return the wrapped {@code GSSCredential}.
+ */
private static GSSCredential wrapCredential(final GSSCredential credential) {
return new GSSCredential() {
diff --git a/mechanism/http/pom.xml b/mechanism/http/pom.xml
index 452ff36091d..394a1de641d 100644
--- a/mechanism/http/pom.xml
+++ b/mechanism/http/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/mechanism/http/src/main/java/org/wildfly/security/mechanism/http/UsernamePasswordAuthenticationMechanism.java b/mechanism/http/src/main/java/org/wildfly/security/mechanism/http/UsernamePasswordAuthenticationMechanism.java
index 328ef1cd200..413848192c5 100644
--- a/mechanism/http/src/main/java/org/wildfly/security/mechanism/http/UsernamePasswordAuthenticationMechanism.java
+++ b/mechanism/http/src/main/java/org/wildfly/security/mechanism/http/UsernamePasswordAuthenticationMechanism.java
@@ -49,13 +49,24 @@ public abstract class UsernamePasswordAuthenticationMechanism implements HttpSer
protected final CallbackHandler callbackHandler;
/**
- * @param callbackHandler
+ * Constructs a new {@code UsernamePasswordAuthenticationMechanism} instance.
+ *
+ * @param callbackHandler the CallbackHandler used for authentication.
*/
protected UsernamePasswordAuthenticationMechanism(CallbackHandler callbackHandler) {
super();
this.callbackHandler = callbackHandler;
}
+ /**
+ * Authenticates the user for provided realm using their username and password.
+ *
+ * @param realmName the realm for which the user is authenticating.
+ * @param username the username of the authenticating user.
+ * @param password the password of the authenticating user.
+ * @return {@code true} if the user is authenticated for the realm, {@code false} otherwise.
+ * @throws HttpAuthenticationException if there was an IOException caused by the CallbackHandler.
+ */
protected boolean authenticate(String realmName, String username, char[] password) throws HttpAuthenticationException {
RealmCallback realmCallback = realmName != null ? new RealmCallback("User realm", realmName) : null;
NameCallback nameCallback = new NameCallback("Remote Authentication Name", username);
@@ -94,6 +105,13 @@ protected boolean authenticate(String realmName, String username, char[] passwor
}
}
+ /**
+ * Checks if the user is authorized.
+ *
+ * @param username the username to authorize.
+ * @return {@code true} if the user is authorized, {@code false} otherwise.
+ * @throws HttpAuthenticationException if there was an IOException caused by the CallbackHandler.
+ */
protected boolean authorize(String username) throws HttpAuthenticationException {
httpUserPass.debugf("Username authorization. Username: [%s].",
username);
@@ -111,10 +129,22 @@ protected boolean authorize(String username) throws HttpAuthenticationException
}
}
+ /**
+ * Sends the information to the callbackHandler that the authorization succeeded.
+ *
+ * @throws IOException if an input or output error occurs.
+ * @throws UnsupportedCallbackException if the implementation of callbackHandler does not support the specified Callback type.
+ */
protected void succeed() throws IOException, UnsupportedCallbackException {
callbackHandler.handle(new Callback[] { AuthenticationCompleteCallback.SUCCEEDED });
}
+ /**
+ * Sends the information to the callbackHandler that the authorization failed.
+ *
+ * @throws IOException if an input or output error occurs.
+ * @throws UnsupportedCallbackException if the implementation of callbackHandler does not support the specified Callback type.
+ */
protected void fail() throws IOException, UnsupportedCallbackException {
callbackHandler.handle(new Callback[] { AuthenticationCompleteCallback.FAILED });
}
diff --git a/mechanism/oauth2/pom.xml b/mechanism/oauth2/pom.xml
index 15efe4ee541..e57683a7787 100644
--- a/mechanism/oauth2/pom.xml
+++ b/mechanism/oauth2/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/mechanism/oauth2/src/main/java/org/wildfly/security/mechanism/oauth2/OAuth2Client.java b/mechanism/oauth2/src/main/java/org/wildfly/security/mechanism/oauth2/OAuth2Client.java
index 315b9c1ee8e..ec78a73eeaa 100644
--- a/mechanism/oauth2/src/main/java/org/wildfly/security/mechanism/oauth2/OAuth2Client.java
+++ b/mechanism/oauth2/src/main/java/org/wildfly/security/mechanism/oauth2/OAuth2Client.java
@@ -33,6 +33,8 @@
import static org.wildfly.common.Assert.assertTrue;
/**
+ * Implementation of the client side of the OAuth2 SASL mechanism.
+ *
* @author Pedro Igor
*/
public class OAuth2Client {
@@ -43,12 +45,26 @@ public class OAuth2Client {
private final String authorizationId;
private ElytronMessages log;
+ /**
+ * Constructs a new {@code OAuth2Client} instance.
+ *
+ * @param authorizationId the ID of the user to be authorized.
+ * @param callbackHandler the callback handler for verifying the Bearer token.
+ * @param log the logger to use.
+ */
public OAuth2Client(String authorizationId, CallbackHandler callbackHandler, ElytronMessages log) {
this.authorizationId = authorizationId;
this.callbackHandler = callbackHandler;
this.log = log;
}
+ /**
+ * Gets the initial response message from the client that will be sent to the server.
+ * It retrieves the Bearer token from a callback and constructs an encoded message that includes the token.
+ *
+ * @return encoded message that includes the Bearer token.
+ * @throws AuthenticationMechanismException if an error occurs during the callback or the token is {@code null}.
+ */
public OAuth2InitialClientMessage getInitialResponse() throws AuthenticationMechanismException {
final CredentialCallback credentialCallback = new CredentialCallback(BearerTokenCredential.class);
@@ -80,13 +96,19 @@ public OAuth2InitialClientMessage getInitialResponse() throws AuthenticationMech
return new OAuth2InitialClientMessage(null, null, encoded.toArray());
}
+ /**
+ * Handles the server's response to the initial client message.
+ *
+ * @param serverMessage the byte array containing the server's response.
+ * @return {@code null} if the response was successful, aborting the authentication otherwise.
+ */
public byte[] handleServerResponse(byte[] serverMessage) {
// got a successful response
if (serverMessage.length == 0) {
return null;
}
- // otherwise, server responded with a error message
+ // otherwise, server responded with an error message
try {
String errorMessage = ByteIterator.ofBytes(serverMessage).asUtf8String().base64Decode().asUtf8String().drainToString();
log.debugf("Got error message from server [%s].", errorMessage);
diff --git a/mechanism/oauth2/src/main/java/org/wildfly/security/mechanism/oauth2/OAuth2InitialClientMessage.java b/mechanism/oauth2/src/main/java/org/wildfly/security/mechanism/oauth2/OAuth2InitialClientMessage.java
index f5b6d6014a4..c244b043213 100644
--- a/mechanism/oauth2/src/main/java/org/wildfly/security/mechanism/oauth2/OAuth2InitialClientMessage.java
+++ b/mechanism/oauth2/src/main/java/org/wildfly/security/mechanism/oauth2/OAuth2InitialClientMessage.java
@@ -19,6 +19,8 @@
package org.wildfly.security.mechanism.oauth2;
/**
+ * Represents the initial client message for OAuth2 protocol.
+ *
* @author Pedro Igor
*/
public class OAuth2InitialClientMessage {
@@ -27,24 +29,51 @@ public class OAuth2InitialClientMessage {
private final byte[] messageBytes;
private final String authorizationId;
+ /**
+ * Constructs a new {@code OAuth2InitialClientMessage} instance.
+ *
+ * @param authorizationId the ID of the user to be authorized.
+ * @param auth the authorization information in form of a String.
+ * @param messageBytes the byte array containing the message.
+ */
public OAuth2InitialClientMessage(String authorizationId, String auth, byte[] messageBytes) {
this.authorizationId = authorizationId;
this.auth = auth;
this.messageBytes = messageBytes;
}
+ /**
+ * Returns the ID of the user to be authorized.
+ *
+ * @return the ID of the user to be authorized.
+ */
public String getAuthorizationId() {
return this.authorizationId;
}
+ /**
+ * Returns the byte array containing the message.
+ *
+ * @return the byte array containing the message.
+ */
public byte[] getMessage() {
return this.messageBytes;
}
+ /**
+ * Returns the authorization information in form of a String.
+ *
+ * @return the authorization information in form of a String.
+ */
public String getAuth() {
return auth;
}
+ /**
+ * Returns whether the client provides a Bearer token.
+ *
+ * @return {@code True} if the authorization information contains "Bearer", {@code false} otherwise.
+ */
public boolean isBearerToken() {
return this.auth.startsWith("Bearer");
}
diff --git a/mechanism/oauth2/src/main/java/org/wildfly/security/mechanism/oauth2/OAuth2Server.java b/mechanism/oauth2/src/main/java/org/wildfly/security/mechanism/oauth2/OAuth2Server.java
index c1c3e52324f..306771e6fc8 100644
--- a/mechanism/oauth2/src/main/java/org/wildfly/security/mechanism/oauth2/OAuth2Server.java
+++ b/mechanism/oauth2/src/main/java/org/wildfly/security/mechanism/oauth2/OAuth2Server.java
@@ -40,7 +40,7 @@
import org.wildfly.security.mechanism.AuthenticationMechanismException;
/**
- * An OAuth2 Sasl Server based on RFC-7628.
+ * An OAuth2 Server based on RFC-7628.
*
* @author Pedro Igor
*/
@@ -53,12 +53,26 @@ public class OAuth2Server {
private final Map serverConfig;
private ElytronMessages log;
+ /**
+ * Constructs a new {@code OAuth2Server} instance.
+ *
+ * @param callbackHandler the callback handler for verifying the Bearer token.
+ * @param serverConfig the server configuration.
+ * @param log the logger to use.
+ */
public OAuth2Server(CallbackHandler callbackHandler, Map serverConfig, ElytronMessages log) {
this.callbackHandler = callbackHandler;
this.serverConfig = serverConfig;
this.log = log;
}
+ /**
+ * Parses the initial client's message in OAuth2 protocol.
+ *
+ * @param fromBytes the initial client's message.
+ * @return parsed client's message.
+ * @throws AuthenticationMechanismException if an error occurs during the parsing or the message is invalid.
+ */
public OAuth2InitialClientMessage parseInitialClientMessage(byte[] fromBytes) throws AuthenticationMechanismException {
byte[] messageBytes = fromBytes.clone();
ByteIterator byteIterator = ByteIterator.ofBytes(fromBytes.clone());
@@ -98,6 +112,13 @@ public OAuth2InitialClientMessage parseInitialClientMessage(byte[] fromBytes) th
}
}
+ /**
+ * Returns the value associated with a key from an OAuth2 message.
+ *
+ * @param key the key for which the value is extracted.
+ * @param keyValuesPart the String containing key-value pairs in form of OAuth2 message.
+ * @return the value of the key-value pair, {@code null} if the key is not found.
+ */
private String getValue(String key, String keyValuesPart) {
for (String current : keyValuesPart.split(KV_DELIMITER)) {
String[] keyValue = current.split("=");
@@ -110,6 +131,14 @@ private String getValue(String key, String keyValuesPart) {
return null;
}
+ /**
+ * Evaluates the initial response sent by the client and verifies if the Bearer token is valid.
+ * If so, authorizes the user.
+ *
+ * @param initialClientMessage the initial client's message containing the Bearer token.
+ * @return an empty byte array if the token was authorized, error message otherwise.
+ * @throws AuthenticationMechanismException if an error occurs during the evaluation or the message doesn't contain the Bearer token.
+ */
public byte[] evaluateInitialResponse(OAuth2InitialClientMessage initialClientMessage) throws AuthenticationMechanismException {
if (initialClientMessage.isBearerToken()) {
String auth = initialClientMessage.getAuth();
@@ -153,6 +182,12 @@ public byte[] evaluateInitialResponse(OAuth2InitialClientMessage initialClientMe
throw log.mechInvalidClientMessage();
}
+ /**
+ * Creates an error message in the format of a json object.
+ *
+ * @return The error message containing a "status" field with the value "invalid_token"
+ * and an optional field "openid-configuration" with {@code CONFIG_OPENID_CONFIGURATION_URL} value.
+ */
private byte[] createErrorMessage() {
JsonObjectBuilder objectBuilder = Json.createObjectBuilder();
diff --git a/mechanism/scram/pom.xml b/mechanism/scram/pom.xml
index e771289f141..bee2afec31a 100644
--- a/mechanism/scram/pom.xml
+++ b/mechanism/scram/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramClient.java b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramClient.java
index 0531242c362..c6cb67e86b9 100644
--- a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramClient.java
+++ b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramClient.java
@@ -48,6 +48,8 @@
import org.wildfly.security.sasl.util.StringPrep;
/**
+ * A client-side implementation for the SCRAM authentication.
+ *
* @author David M. Lloyd
*/
public final class ScramClient {
@@ -61,6 +63,19 @@ public final class ScramClient {
private final int minimumIterationCount;
private final int maximumIterationCount;
+ /**
+ * Constructs a new {@code ScramClient} instance.
+ *
+ * @param mechanism the SCRAM mechanism used for the authentication.
+ * @param authorizationId the ID of the user to be authorized.
+ * @param callbackHandler the callbackHandler used for the authentication.
+ * @param secureRandom an optional secure RNG to use.
+ * @param bindingData the binding data for the "PLUS" channel binding option.
+ * @param bindingType the binding type for the "PLUS" channel binding option.
+ * @param minimumIterationCount the minimum number of iterations for password hashing.
+ * @param maximumIterationCount the maximum number of iterations for password hashing.
+ * @param providers the security providers.
+ */
ScramClient(final ScramMechanism mechanism, final String authorizationId, final CallbackHandler callbackHandler, final SecureRandom secureRandom, final byte[] bindingData, final String bindingType, final int minimumIterationCount, final int maximumIterationCount, final Supplier providers) {
this.mechanism = mechanism;
this.authorizationId = authorizationId;
@@ -73,26 +88,56 @@ public final class ScramClient {
this.providers = providers;
}
+ /**
+ * Returns the secure RNG used for the authentication.
+ *
+ * @return the secure RNG used for the authentication.
+ */
Random getRandom() {
return secureRandom != null ? secureRandom : ThreadLocalRandom.current();
}
+ /**
+ * Returns the SCRAM mechanism used for the authentication.
+ *
+ * @return the SCRAM mechanism used for the authentication.
+ */
public ScramMechanism getMechanism() {
return mechanism;
}
+ /**
+ * Returns the ID of the user to be authorized.
+ *
+ * @return the ID of the user to be authorized.
+ */
public String getAuthorizationId() {
return authorizationId;
}
+ /**
+ * Returns the binding type for the "PLUS" channel binding option.
+ *
+ * @return the binding type for the "PLUS" channel binding option.
+ */
public String getBindingType() {
return bindingType;
}
+ /**
+ * Returns the binding data for the "PLUS" channel binding option.
+ *
+ * @return the binding data for the "PLUS" channel binding option.
+ */
byte[] getRawBindingData() {
return bindingData;
}
+ /**
+ * Returns a copy of the binding data for the "PLUS" channel binding option.
+ *
+ * @return a copy of the binding data for the "PLUS" channel binding option.
+ */
public byte[] getBindingData() {
final byte[] bindingData = this.bindingData;
return bindingData == null ? null : bindingData.clone();
@@ -146,6 +191,15 @@ public ScramInitialClientMessage getInitialResponse() throws AuthenticationMecha
return new ScramInitialClientMessage(this, name, binding, nonce, initialPartIndex, encoded.toArray());
}
+ /**
+ * Parses the initial server message and creates {@link ScramInitialServerMessage} from parsed information.
+ * Also checks if the message have all necessary properties.
+ *
+ * @param initialResponse the initial client response for the server.
+ * @param bytes the byte array containing the initial server message to parse.
+ * @return the initial server message.
+ * @throws AuthenticationMechanismException if an error occurs during the parsing.
+ */
public ScramInitialServerMessage parseInitialServerMessage(final ScramInitialClientMessage initialResponse, final byte[] bytes) throws AuthenticationMechanismException {
final byte[] challenge = bytes.clone();
final ByteIterator bi = ByteIterator.ofBytes(challenge);
@@ -190,6 +244,18 @@ public ScramInitialServerMessage parseInitialServerMessage(final ScramInitialCli
return new ScramInitialServerMessage(initialResponse, serverNonce, salt, iterationCount, challenge);
}
+ /**
+ * Handles the initial challenge from the server and create a response from the client.
+ * The method uses a password credential obtained from the callback handler to derive a salted password,
+ * which is then used to generate a client key, stored key, and client proof.
+ *
+ * @param initialResponse the initial client message.
+ * @param initialChallenge the initial server message.
+ * @return the final client message.
+ * @throws AuthenticationMechanismException if an error occurs while obtaining the password,
+ * creating the {@link ScramFinalClientMessage} or the mechanism in the initial response or challenge message
+ * does not match the mechanism expected by the server
+ */
public ScramFinalClientMessage handleInitialChallenge(ScramInitialClientMessage initialResponse, ScramInitialServerMessage initialChallenge) throws AuthenticationMechanismException {
boolean trace = saslScram.isTraceEnabled();
@@ -288,6 +354,14 @@ public ScramFinalClientMessage handleInitialChallenge(ScramInitialClientMessage
}
}
+ /**
+ * Parses the final server message and creates {@link ScramFinalServerMessage} from parsed information.
+ * Also checks if the message have all necessary properties.
+ *
+ * @param messageBytes the byte array of the final server message.
+ * @return the final server message.
+ * @throws AuthenticationMechanismException if an error occurs during the parsing or the server rejected the authentication request.
+ */
public ScramFinalServerMessage parseFinalServerMessage(final byte[] messageBytes) throws AuthenticationMechanismException {
final ByteIterator bi = ByteIterator.ofBytes(messageBytes);
final byte[] sig;
@@ -312,6 +386,13 @@ public ScramFinalServerMessage parseFinalServerMessage(final byte[] messageBytes
return new ScramFinalServerMessage(sig, messageBytes);
}
+ /**
+ * Verifies the final challenge received from the server.
+ *
+ * @param finalResponse the final client message.
+ * @param finalChallenge the final server message.
+ * @throws AuthenticationMechanismException if an error occurs during the verification or the server signature is invalid.
+ */
public void verifyFinalChallenge(final ScramFinalClientMessage finalResponse, final ScramFinalServerMessage finalChallenge) throws AuthenticationMechanismException {
boolean trace = saslScram.isTraceEnabled();
diff --git a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramFinalClientMessage.java b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramFinalClientMessage.java
index ed77a508121..066f1ff8bde 100644
--- a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramFinalClientMessage.java
+++ b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramFinalClientMessage.java
@@ -21,6 +21,8 @@
import org.wildfly.security.password.interfaces.ScramDigestPassword;
/**
+ * Final client message for the SCRAM authentication.
+ *
* @author David M. Lloyd
*/
public final class ScramFinalClientMessage {
@@ -32,6 +34,16 @@ public final class ScramFinalClientMessage {
private final byte[] messageBytes;
private final int proofOffset;
+ /**
+ * Constructs a new {@code ScramFinalClientMessage} instance.
+ *
+ * @param initialResponse the initial client message.
+ * @param initialChallenge the initial server message.
+ * @param password the password used for authentication.
+ * @param clientProof the client proof sent to the server.
+ * @param messageBytes the byte array of the message.
+ * @param proofOffset the proof location in the {@code messageBytes}.
+ */
ScramFinalClientMessage(final ScramInitialClientMessage initialResponse, final ScramInitialServerMessage initialChallenge, final ScramDigestPassword password, final byte[] clientProof, final byte[] messageBytes, final int proofOffset) {
this.initialResponse = initialResponse;
this.initialChallenge = initialChallenge;
@@ -41,38 +53,83 @@ public final class ScramFinalClientMessage {
this.proofOffset = proofOffset;
}
+ /**
+ * Returns the initial client message.
+ *
+ * @return the initial client message.
+ */
public ScramInitialClientMessage getInitialResponse() {
return initialResponse;
}
+ /**
+ * Returns the initial server message.
+ *
+ * @return the initial server message.
+ */
public ScramInitialServerMessage getInitialChallenge() {
return initialChallenge;
}
+ /**
+ * Returns the password used for authentication.
+ *
+ * @return the password used for authentication.
+ */
public ScramDigestPassword getPassword() {
return password;
}
+ /**
+ * Returns the client proof sent to the server.
+ *
+ * @return the client proof sent to the server.
+ */
byte[] getRawClientProof() {
return clientProof;
}
+ /**
+ * Returns the byte array of the message.
+ *
+ * @return the byte array of the message.
+ */
byte[] getRawMessageBytes() {
return messageBytes;
}
+ /**
+ * Returns a copy of the client proof sent to the server.
+ *
+ * @return a copy of the client proof sent to the server.
+ */
public byte[] getClientProof() {
return clientProof.clone();
}
+ /**
+ * Returns a copy of the byte array of the message.
+ *
+ * @return a copy of the byte array of the message.
+ */
public byte[] getMessageBytes() {
return messageBytes.clone();
}
+ /**
+ * Returns the SCRAM mechanism in the initial client message.
+ *
+ * @return the SCRAM mechanism in the initial client message.
+ */
public ScramMechanism getMechanism() {
return initialResponse.getMechanism();
}
+ /**
+ * Returns the proof location in the message.
+ *
+ * @return the proof location in the message.
+ */
int getProofOffset() {
return proofOffset;
}
diff --git a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramFinalServerMessage.java b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramFinalServerMessage.java
index c8b77f2c922..1a4470ac7a7 100644
--- a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramFinalServerMessage.java
+++ b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramFinalServerMessage.java
@@ -19,29 +19,57 @@
package org.wildfly.security.mechanism.scram;
/**
+ * Final server message for the SCRAM authentication.
+ *
* @author David M. Lloyd
*/
public final class ScramFinalServerMessage {
private final byte[] serverSignature;
private final byte[] messageBytes;
+ /**
+ * Constructs a new {@code ScramFinalServerMessage} instance.
+ *
+ * @param serverSignature the server signature sent to the client in form of the byte array.
+ * @param messageBytes the final server message in form of byte array.
+ */
ScramFinalServerMessage(final byte[] serverSignature, final byte[] messageBytes) {
this.serverSignature = serverSignature;
this.messageBytes = messageBytes;
}
+ /**
+ * Returns the server signature sent to the client in form of the byte array.
+ *
+ * @return the server signature sent to the client in form of the byte array.
+ */
byte[] getRawServerSignature() {
return serverSignature;
}
+ /**
+ * Returns the final server message in form of byte array.
+ *
+ * @return the final server message in form of byte array.
+ */
byte[] getRawMessageBytes() {
return messageBytes;
}
+ /**
+ * Returns a copy of the server signature sent to the client in form of the byte array.
+ *
+ * @return a copy of the server signature sent to the client in form of the byte array.
+ */
public byte[] getServerSignature() {
return serverSignature.clone();
}
+ /**
+ * Returns a copy of the final server message in form of byte array.
+ *
+ * @return a copy of the final server message in form of byte array.
+ */
public byte[] getMessageBytes() {
return messageBytes.clone();
}
diff --git a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramInitialClientMessage.java b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramInitialClientMessage.java
index 62c6eb212b0..e5e9fa0de15 100644
--- a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramInitialClientMessage.java
+++ b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramInitialClientMessage.java
@@ -21,6 +21,8 @@
import java.util.Arrays;
/**
+ * Initial client message for the SCRAM authentication.
+ *
* @author David M. Lloyd
*/
public final class ScramInitialClientMessage {
@@ -34,6 +36,16 @@ public final class ScramInitialClientMessage {
private final int initialPartIndex;
private final byte[] messageBytes;
+ /**
+ * Constructs a new {@code ScramInitialClientMessage} instance using data from the {@code scramClient}.
+ *
+ * @param scramClient the SCRAM client providing binding type and data, SCRAM mechanism and authorization ID.
+ * @param authenticationName the name of the user that is authenticated.
+ * @param binding whether the client supports channel binding.
+ * @param nonce a unique value generated by the client to the server.
+ * @param initialPartIndex index of the initial part of the message.
+ * @param messageBytes the byte array of the message.
+ */
ScramInitialClientMessage(final ScramClient scramClient, final String authenticationName, final boolean binding, final byte[] nonce, final int initialPartIndex, final byte[] messageBytes) {
this.binding = binding;
this.initialPartIndex = initialPartIndex;
@@ -46,6 +58,19 @@ public final class ScramInitialClientMessage {
this.messageBytes = messageBytes;
}
+ /**
+ * Constructs a new {@code ScramInitialClientMessage} instance.
+ *
+ * @param mechanism the SCRAM mechanism used for the authentication.
+ * @param authorizationId the ID of the user to be authorized.
+ * @param authenticationName the name of the user that is authenticated.
+ * @param binding whether the client supports channel binding.
+ * @param bindingType the binding type for the "PLUS" channel binding option.
+ * @param bindingData the binding data for the "PLUS" channel binding option.
+ * @param nonce a unique value generated by the client to the server.
+ * @param initialPartIndex index of the initial part of the message.
+ * @param messageBytes the byte array of the message.
+ */
ScramInitialClientMessage(final ScramMechanism mechanism, final String authorizationId, final String authenticationName, final boolean binding, final String bindingType, final byte[] bindingData, final byte[] nonce, final int initialPartIndex, final byte[] messageBytes) {
this.mechanism = mechanism;
this.authorizationId = authorizationId;
@@ -58,54 +83,119 @@ public final class ScramInitialClientMessage {
this.messageBytes = messageBytes;
}
+ /**
+ * Returns the SCRAM mechanism used for the authentication.
+ *
+ * @return the SCRAM mechanism used for the authentication.
+ */
public ScramMechanism getMechanism() {
return mechanism;
}
+ /**
+ * Returns the name of the user that is authenticated.
+ *
+ * @return the name of the user that is authenticated.
+ */
public String getAuthenticationName() {
return authenticationName;
}
+ /**
+ * Returns a copy of a unique value generated by the client to the server.
+ *
+ * @return a copy of a unique value generated by the client to the server.
+ */
public byte[] getNonce() {
return nonce.clone();
}
+ /**
+ * Returns a unique value generated by the client to the server.
+ *
+ * @return a unique value generated by the client to the server.
+ */
byte[] getRawNonce() {
return nonce;
}
+ /**
+ * Returns the initial part of the message.
+ *
+ * @return the initial part of the message up to the length of {@code initialPartIndex}.
+ */
public byte[] getInitialPart() {
return Arrays.copyOfRange(messageBytes, 0, initialPartIndex);
}
+ /**
+ * Returns a copy of the byte array of the message.
+ *
+ * @return a copy of the byte array of the message.
+ */
public byte[] getMessageBytes() {
return messageBytes.clone();
}
+ /**
+ * Returns the ID of the user to be authorized.
+ *
+ * @return the ID of the user to be authorized.
+ */
public String getAuthorizationId() {
return authorizationId;
}
+ /**
+ * Returns whether the client supports channel binding.
+ *
+ * @return {@code true} if the client supports channel binding, {@code false} otherwise.
+ */
public boolean isBinding() {
return binding;
}
+ /**
+ * Returns the binding type for the "PLUS" channel binding option.
+ *
+ * @return the binding type for the "PLUS" channel binding option.
+ */
public String getBindingType() {
return bindingType;
}
+ /**
+ * Returns a copy of the binding data for the "PLUS" channel binding option.
+ *
+ * @return a copy of the binding data for the "PLUS" channel binding option.
+ */
public byte[] getBindingData() {
return bindingData == null ? null : bindingData.clone();
}
+ /**
+ * Returns the binding data for the "PLUS" channel binding option.
+ *
+ * @return the binding data for the "PLUS" channel binding option.
+ */
byte[] getRawBindingData() {
return bindingData;
}
+ /**
+ * Returns index of the initial part of the message.
+ *
+ * @return index of the initial part of the message.
+ */
int getInitialPartIndex() {
return initialPartIndex;
}
+ /**
+ * Returns the byte array of the message.
+ *
+ * @return the byte array of the message.
+ */
byte[] getRawMessageBytes() {
return messageBytes;
}
diff --git a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramInitialServerMessage.java b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramInitialServerMessage.java
index fd0b0d9306b..8e5f24a78bf 100644
--- a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramInitialServerMessage.java
+++ b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramInitialServerMessage.java
@@ -19,6 +19,8 @@
package org.wildfly.security.mechanism.scram;
/**
+ * Initial server message for the SCRAM authentication.
+ *
* @author David M. Lloyd
*/
public final class ScramInitialServerMessage {
@@ -28,6 +30,15 @@ public final class ScramInitialServerMessage {
private final int iterationCount;
private final byte[] messageBytes;
+ /**
+ * Constructs a new {@code ScramInitialServerMessage} instance.
+ *
+ * @param initialResponse the initial client message that this initial server message is responding to.
+ * @param serverNonce the server generated nonce.
+ * @param salt the salt used for generating salted password.
+ * @param iterationCount the iteration count used for generating salted password.
+ * @param messageBytes the message in form of byte array.
+ */
ScramInitialServerMessage(final ScramInitialClientMessage initialResponse, final byte[] serverNonce, final byte[] salt, final int iterationCount, final byte[] messageBytes) {
this.initialResponse = initialResponse;
this.serverNonce = serverNonce;
@@ -36,38 +47,83 @@ public final class ScramInitialServerMessage {
this.messageBytes = messageBytes;
}
+ /**
+ * Returns the SCRAM mechanism in the initial client message.
+ *
+ * @return the SCRAM mechanism in the initial client message.
+ */
public ScramMechanism getMechanism() {
return initialResponse.getMechanism();
}
+ /**
+ * Returns the initial client message.
+ *
+ * @return the initial client message.
+ */
public ScramInitialClientMessage getInitialResponse() {
return initialResponse;
}
+ /**
+ * Returns a copy of the server nonce.
+ *
+ * @return a copy of the server nonce.
+ */
public byte[] getServerNonce() {
return serverNonce.clone();
}
+ /**
+ * Returns the server nonce.
+ *
+ * @return the server nonce.
+ */
byte[] getRawServerNonce() {
return serverNonce;
}
+ /**
+ * Returns the iteration count used for generating salted password.
+ *
+ * @return the iteration count used for generating salted password.
+ */
public int getIterationCount() {
return iterationCount;
}
+ /**
+ * Returns the salt used for generating salted password.
+ *
+ * @return the salt used for generating salted password.
+ */
byte[] getRawSalt() {
return salt;
}
+ /**
+ * Returns the initial server message in form of byte array.
+ *
+ * @return the initial server message in form of byte array.
+ */
byte[] getRawMessageBytes() {
return messageBytes;
}
+ /**
+ * Returns a copy of the salt used for generating salted password.
+ *
+ * @return a copy of the salt used for generating salted password.
+ */
public byte[] getSalt() {
return salt.clone();
}
+ /**
+ * Returns a copy of the message in form of byte array.
+ *
+ * @return a copy of the message in form of byte array.
+ */
public byte[] getMessageBytes() {
return messageBytes.clone();
}
diff --git a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramInitialServerResult.java b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramInitialServerResult.java
index 0c0df7332c2..07ff6d4f93c 100644
--- a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramInitialServerResult.java
+++ b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramInitialServerResult.java
@@ -21,21 +21,39 @@
import org.wildfly.security.password.interfaces.ScramDigestPassword;
/**
+ * A class for encapsulation of the initial SCRAM challenge and the digest password.
+ *
* @author David M. Lloyd
*/
public final class ScramInitialServerResult {
private final ScramInitialServerMessage scramInitialChallenge;
private final ScramDigestPassword scramDigestPassword;
+ /**
+ * Constructs a new {@code ScramInitialServerResult}.
+ *
+ * @param scramInitialChallenge the SCRAM challenge message.
+ * @param scramDigestPassword the digest password for the SCRAM authentication.
+ */
ScramInitialServerResult(final ScramInitialServerMessage scramInitialChallenge, final ScramDigestPassword scramDigestPassword) {
this.scramInitialChallenge = scramInitialChallenge;
this.scramDigestPassword = scramDigestPassword;
}
+ /**
+ * Returns the SCRAM challenge message.
+ *
+ * @return ScramInitialServerMessage
+ */
public ScramInitialServerMessage getScramInitialChallenge() {
return scramInitialChallenge;
}
+ /**
+ * Returns the digest password for the SCRAM authentication.
+ *
+ * @return ScramDigestPassword
+ */
public ScramDigestPassword getScramDigestPassword() {
return scramDigestPassword;
}
diff --git a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramMechanism.java b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramMechanism.java
index 7f5ad535398..80ae5e41a93 100644
--- a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramMechanism.java
+++ b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramMechanism.java
@@ -30,10 +30,12 @@
import org.wildfly.security.sasl.WildFlySasl;
/**
+ * Implementation of the SCRAM authentication mechanism.
+ *
* @author David M. Lloyd
*/
public final class ScramMechanism {
- /** Hash size; may be less than the output size of the MD/MAC */
+ // Hash size; may be less than the output size of the MD/MAC
private final int hashSize;
private final String messageDigestName;
private final String hmacName;
@@ -41,6 +43,15 @@ public final class ScramMechanism {
private final String passwordAlgorithm;
private final String toString;
+ /**
+ * Constructs a new {@code ScramMechanism}.
+ *
+ * @param hashSize the size of the hash of the SCRAM mechanism.
+ * @param messageDigestName the name of the message digest algorithm.
+ * @param hmacName the name of the HMAC algorithm.
+ * @param plus {@code true} to use the PLUS channel binding, {@code false} otherwise.
+ * @param passwordAlgorithm the name of the password algorithm in {@link ScramDigestPassword}.
+ */
private ScramMechanism(final int hashSize, final String messageDigestName, final String hmacName, final boolean plus, final String passwordAlgorithm) {
this.hashSize = hashSize;
this.messageDigestName = messageDigestName;
@@ -75,6 +86,7 @@ private ScramMechanism(final int hashSize, final String messageDigestName, final
* @param bindingCallback the optional channel binding callback result (may be {@code null})
* @param minimumIterationCount the minimum iteration count to allow
* @param maximumIterationCount the maximum iteration count to allow
+ * @param providers the security providers.
* @return the SCRAM client, or {@code null} if the client cannot be created from this mechanism variant
* @throws AuthenticationMechanismException if the mechanism fails for some reason
* @see WildFlySasl#SCRAM_MIN_ITERATION_COUNT
@@ -94,6 +106,18 @@ public ScramClient createClient(final String authorizationId, final CallbackHand
return new ScramClient(this, authorizationId, callbackHandler, secureRandom, bindingData, bindingType, minimumIterationCount, maximumIterationCount, providers);
}
+ /**
+ * Create a SCRAM server for this mechanism.
+ *
+ * @param callbackHandler the callback handler (may not be {@code null}).
+ * @param random an optional secure random implementation to use (may be {@code null}).
+ * @param bindingCallback the optional channel binding callback result (may be {@code null}).
+ * @param minimumIterationCount the minimum iteration count to allow.
+ * @param maximumIterationCount the maximum iteration count to allow.
+ * @param providers the security providers.
+ * @return the SCRAM server, or {@code null} if the server cannot be created from this mechanism variant.
+ * @throws AuthenticationMechanismException if the mechanism fails for some reason.
+ */
public ScramServer createServer(final CallbackHandler callbackHandler, final SecureRandom random, final ChannelBindingCallback bindingCallback, final int minimumIterationCount, final int maximumIterationCount, final Supplier providers) throws AuthenticationMechanismException {
final byte[] bindingData;
final String bindingType;
@@ -108,26 +132,57 @@ public ScramServer createServer(final CallbackHandler callbackHandler, final Sec
return new ScramServer(this, callbackHandler, random, bindingData, bindingType, minimumIterationCount, maximumIterationCount, providers);
}
+ /**
+ * Returns the size of the hash of the SCRAM mechanism.
+ *
+ * @return the size of the hash of the SCRAM mechanism.
+ */
public int getHashSize() {
return hashSize;
}
+ /**
+ * Returns the name of the message digest algorithm.
+ *
+ * @return the name of the message digest algorithm.
+ */
public String getMessageDigestName() {
return messageDigestName;
}
+ /**
+ * Returns the name of the HMAC algorithm.
+ *
+ * @return the name of the HMAC algorithm.
+ */
public String getHmacName() {
return hmacName;
}
+ /**
+ * Returns whether the SCRAM mechanism uses the PLUS channel binding.
+ *
+ * @return {@code true} to use the PLUS channel binding, {@code false} otherwise.
+ */
public boolean isPlus() {
return plus;
}
+ /**
+ * Returns the name of the password algorithm from {@code ScramDigestPassword}.
+ *
+ * @return the name of the password algorithm.
+ */
public String getPasswordAlgorithm() {
return passwordAlgorithm;
}
+ /**
+ * Returns a String representation of the SCRAM mechanism.
+ * Contains the Digest name, PLUS channel binding and hash size.
+ *
+ * @return a String representation of the SCRAM mechanism.
+ */
public String toString() {
return toString;
}
diff --git a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramServer.java b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramServer.java
index ab7360b9b50..2189a52bdb8 100644
--- a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramServer.java
+++ b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramServer.java
@@ -55,6 +55,8 @@
import org.wildfly.security.sasl.util.StringPrep;
/**
+ * A server-side implementation for the SCRAM authentication.
+ *
* @author David M. Lloyd
*/
public final class ScramServer {
@@ -67,6 +69,18 @@ public final class ScramServer {
private final int minimumIterationCount;
private final int maximumIterationCount;
+ /**
+ * Constructs a new {@code ScramServer}.
+ *
+ * @param mechanism the SCRAM mechanism used for the authentication.
+ * @param callbackHandler the callback handler for the authentication.
+ * @param random an optional secure RNG to use.
+ * @param bindingData the binding data for the "PLUS" channel binding option.
+ * @param bindingType the binding type for the "PLUS" channel binding option.
+ * @param minimumIterationCount the minimum number of iterations for password hashing.
+ * @param maximumIterationCount the maximum number of iterations for password hashing.
+ * @param providers the security providers.
+ */
ScramServer(final ScramMechanism mechanism, final CallbackHandler callbackHandler, final SecureRandom random, final byte[] bindingData, final String bindingType, final int minimumIterationCount, final int maximumIterationCount, final Supplier providers) {
this.mechanism = mechanism;
this.callbackHandler = callbackHandler;
@@ -185,6 +199,14 @@ public ScramInitialClientMessage parseInitialClientMessage(ChannelBindingCallbac
}
}
+ /**
+ * Evaluates the initial client response message in SCRAM authentication.
+ * Generates a server nonce and salted password.
+ *
+ * @param clientMessage the initial client response message.
+ * @return the initial server result, containing the initial server message and the digest password.
+ * @throws AuthenticationMechanismException if an error occurs during the evaluation.
+ */
public ScramInitialServerResult evaluateInitialResponse(final ScramInitialClientMessage clientMessage) throws AuthenticationMechanismException {
final boolean trace = saslScram.isTraceEnabled();
@@ -240,6 +262,16 @@ public ScramInitialServerResult evaluateInitialResponse(final ScramInitialClient
return new ScramInitialServerResult(new ScramInitialServerMessage(clientMessage, serverNonce, salt, iterationCount, messageBytes), password);
}
+ /**
+ * Parses the final client message and constructs the {@link ScramFinalClientMessage} from this parsed information.
+ * Also checks if the message has all necessary properties.
+ *
+ * @param initialResponse the initial client response message provided by {@link ScramServer#parseInitialClientMessage(ChannelBindingCallback, byte[])}.
+ * @param initialResult the initial server result provided by {@link ScramServer#evaluateInitialResponse(ScramInitialClientMessage)}.
+ * @param bytes the byte array representation of the client response.
+ * @return the final client message.
+ * @throws AuthenticationMechanismException if an error occurs during the parsing.
+ */
public ScramFinalClientMessage parseFinalClientMessage(final ScramInitialClientMessage initialResponse, final ScramInitialServerResult initialResult, final byte[] bytes) throws AuthenticationMechanismException {
final ScramInitialServerMessage initialChallenge = initialResult.getScramInitialChallenge();
Assert.checkNotNullParam("initialResponse", initialResponse);
@@ -352,6 +384,14 @@ public ScramFinalClientMessage parseFinalClientMessage(final ScramInitialClientM
}
}
+ /**
+ * Evaluates a SCRAM final client message and authorizes the user.
+ *
+ * @param initialResult the result of the initial server message evaluation provided by {@link ScramServer#evaluateInitialResponse(ScramInitialClientMessage)}.
+ * @param clientMessage the final client message provided by {@link ScramServer#parseFinalClientMessage(ScramInitialClientMessage, ScramInitialServerResult, byte[])}.
+ * @return the final server message providing the server signature and response.
+ * @throws AuthenticationMechanismException if an error occurs during the evaluation.
+ */
public ScramFinalServerMessage evaluateFinalClientMessage(final ScramInitialServerResult initialResult, final ScramFinalClientMessage clientMessage) throws AuthenticationMechanismException {
final boolean trace = saslScram.isTraceEnabled();
@@ -464,26 +504,56 @@ public ScramFinalServerMessage evaluateFinalClientMessage(final ScramInitialServ
}
}
+ /**
+ * Returns the SCRAM mechanism used for the authentication.
+ *
+ * @return the SCRAM mechanism used for the authentication.
+ */
public ScramMechanism getMechanism() {
return mechanism;
}
+ /**
+ * Returns the callback handler for the authentication.
+ *
+ * @return the callback handler for the authentication.
+ */
public CallbackHandler getCallbackHandler() {
return callbackHandler;
}
+ /**
+ * Returns the RNG used for the authentication.
+ *
+ * @return the RNG used for the authentication.
+ */
Random getRandom() {
return random != null ? random : ThreadLocalRandom.current();
}
+ /**
+ * Returns the copy of the binding data for the "PLUS" channel binding option.
+ *
+ * @return the copy of the binding data for the "PLUS" channel binding option.
+ */
public byte[] getBindingData() {
return bindingData == null ? null : bindingData.clone();
}
+ /**
+ * Returns the binding data for the "PLUS" channel binding option.
+ *
+ * @return the binding data for the "PLUS" channel binding option.
+ */
byte[] getRawBindingData() {
return bindingData;
}
+ /**
+ * Returns the binding type for the "PLUS" channel binding option.
+ *
+ * @return the binding type for the "PLUS" channel binding option.
+ */
public String getBindingType() {
return bindingType;
}
diff --git a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramUtil.java b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramUtil.java
index dacdf27579a..6180f70e28f 100644
--- a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramUtil.java
+++ b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramUtil.java
@@ -26,6 +26,8 @@
import org.wildfly.common.iteration.ByteIterator;
/**
+ * Common utility functions used by SCRAM authentication mechanism.
+ *
* @author David M. Lloyd
*/
class ScramUtil {
@@ -47,6 +49,13 @@ class ScramUtil {
randomCharDictionary = dict;
}
+ /**
+ * Generates nonce of specified length.
+ *
+ * @param length the length of the nonce.
+ * @param random the RNG used for creating the nonce.
+ * @return a byte array containing the nonce.
+ */
public static byte[] generateNonce(int length, Random random) {
final byte[] chars = new byte[length];
for (int i = 0; i < length; i ++) {
@@ -55,6 +64,13 @@ public static byte[] generateNonce(int length, Random random) {
return chars;
}
+ /**
+ * Parses positive integer from provided ByteIterator.
+ *
+ * @param i the ByteIterator to parse the positive integer from.
+ * @return the parsed integer.
+ * @throws NumberFormatException if the ByteIterator doesn't contain number or the number is too big for an integer
+ */
public static int parsePosInt(final ByteIterator i) {
int a, c;
if (! i.hasNext()) {
@@ -80,6 +96,13 @@ public static int parsePosInt(final ByteIterator i) {
return a;
}
+ /**
+ * Bitwise XOR operation between two byte arrays of the same length.
+ * XOR operation returns 1 if only one of two corresponding bits is 1. For example: 0101 and 0011 gives 0110.
+ *
+ * @param hash the first byte array for the XOR operation. This byte array is modified by the method in place
+ * @param input the second byte array for the XOR operation.
+ */
static void xor(final byte[] hash, final byte[] input) {
assert hash.length == input.length;
for (int i = 0; i < hash.length; i++) {
diff --git a/password/impl/pom.xml b/password/impl/pom.xml
index 3730706429b..2e71afafcf7 100644
--- a/password/impl/pom.xml
+++ b/password/impl/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/password/impl/src/main/java/org/wildfly/security/password/impl/BSDUnixDESCryptPasswordImpl.java b/password/impl/src/main/java/org/wildfly/security/password/impl/BSDUnixDESCryptPasswordImpl.java
index cb918d2ebe6..c7fb6a44b5b 100644
--- a/password/impl/src/main/java/org/wildfly/security/password/impl/BSDUnixDESCryptPasswordImpl.java
+++ b/password/impl/src/main/java/org/wildfly/security/password/impl/BSDUnixDESCryptPasswordImpl.java
@@ -30,7 +30,6 @@
import java.security.spec.InvalidParameterSpecException;
import java.security.spec.KeySpec;
import java.util.Arrays;
-import java.util.concurrent.ThreadLocalRandom;
import org.wildfly.security.password.interfaces.BSDUnixDESCryptPassword;
import org.wildfly.security.password.spec.ClearPasswordSpec;
@@ -72,11 +71,11 @@ class BSDUnixDESCryptPasswordImpl extends AbstractPasswordImpl implements BSDUni
}
BSDUnixDESCryptPasswordImpl(final ClearPasswordSpec passwordSpec) throws InvalidKeySpecException {
- this(passwordSpec.getEncodedPassword(), ThreadLocalRandom.current().nextInt() & 0xffffff, DEFAULT_ITERATION_COUNT);
+ this(passwordSpec.getEncodedPassword(), PasswordUtil.generateRandomSaltInt() & 0xffffff, DEFAULT_ITERATION_COUNT);
}
BSDUnixDESCryptPasswordImpl(final char[] password, final Charset hashCharset) throws InvalidKeySpecException, InvalidParameterSpecException {
- this(password, ThreadLocalRandom.current().nextInt() & 0xffffff, DEFAULT_ITERATION_COUNT, hashCharset);
+ this(password, PasswordUtil.generateRandomSaltInt() & 0xffffff, DEFAULT_ITERATION_COUNT, hashCharset);
}
BSDUnixDESCryptPasswordImpl(final char[] password, final IteratedSaltedPasswordAlgorithmSpec spec, final Charset hashCharset) throws InvalidKeySpecException, InvalidParameterSpecException {
@@ -84,7 +83,7 @@ class BSDUnixDESCryptPasswordImpl extends AbstractPasswordImpl implements BSDUni
}
BSDUnixDESCryptPasswordImpl(final char[] password, final IteratedPasswordAlgorithmSpec spec, final Charset hashCharset) throws InvalidKeySpecException, InvalidParameterSpecException {
- this(password, ThreadLocalRandom.current().nextInt() & 0xffffff, spec.getIterationCount(), hashCharset);
+ this(password, PasswordUtil.generateRandomSaltInt() & 0xffffff, spec.getIterationCount(), hashCharset);
}
BSDUnixDESCryptPasswordImpl(final char[] password, final SaltedPasswordAlgorithmSpec spec, final Charset hashCharset) throws InvalidKeySpecException, InvalidParameterSpecException {
diff --git a/password/impl/src/main/java/org/wildfly/security/password/impl/PasswordUtil.java b/password/impl/src/main/java/org/wildfly/security/password/impl/PasswordUtil.java
index 21e1f8a58f1..9327f4b5ac0 100644
--- a/password/impl/src/main/java/org/wildfly/security/password/impl/PasswordUtil.java
+++ b/password/impl/src/main/java/org/wildfly/security/password/impl/PasswordUtil.java
@@ -17,7 +17,7 @@
*/
package org.wildfly.security.password.impl;
-import java.util.concurrent.ThreadLocalRandom;
+import org.wildfly.common.Assert;
/**
* Helper utility methods for operations on passwords.
@@ -27,6 +27,8 @@
*/
final class PasswordUtil {
+ private static final ThreadLocalSecureRandom THREAD_LOCAL_SECURE_RANDOM = new ThreadLocalSecureRandom();
+
/**
* Generate a random salt as byte array.
*
@@ -35,7 +37,22 @@ final class PasswordUtil {
*/
public static byte[] generateRandomSalt(int saltSize) {
byte[] randomSalt = new byte[saltSize];
- ThreadLocalRandom.current().nextBytes(randomSalt);
+ THREAD_LOCAL_SECURE_RANDOM.get().nextBytes(randomSalt);
return randomSalt;
}
+
+ /**
+ * Generate a random salt as int.
+ *
+ * @return a byte array representing the random salt
+ */
+ static int generateRandomSaltInt() {
+ byte[] saltBytes = generateRandomSalt(4);
+ return convertBytesToInt(saltBytes);
+ }
+
+ static int convertBytesToInt(byte[] saltBytes) {
+ Assert.assertTrue(saltBytes.length == 4);
+ return (saltBytes[0] & 0xff) << 24 | (saltBytes[1] & 0xff) << 16 | (saltBytes[2] & 0xff) << 8 | saltBytes[3] & 0xff;
+ }
}
diff --git a/password/impl/src/main/java/org/wildfly/security/password/impl/ThreadLocalSecureRandom.java b/password/impl/src/main/java/org/wildfly/security/password/impl/ThreadLocalSecureRandom.java
new file mode 100644
index 00000000000..5a99da754e8
--- /dev/null
+++ b/password/impl/src/main/java/org/wildfly/security/password/impl/ThreadLocalSecureRandom.java
@@ -0,0 +1,32 @@
+/*
+ * JBoss, Home of Professional Open Source
+ *
+ * Copyright 2024 Red Hat, Inc. and/or its affiliates.
+ *
+ * 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.password.impl;
+
+import java.security.SecureRandom;
+import java.util.function.Supplier;
+
+class ThreadLocalSecureRandom implements Supplier {
+ final ThreadLocal localInstance = new ThreadLocal<>();
+
+ public SecureRandom get() {
+ if (localInstance.get() == null) {
+ localInstance.set(new SecureRandom());
+ }
+ return localInstance.get();
+ }
+}
diff --git a/password/impl/src/main/java/org/wildfly/security/password/impl/UnixDESCryptPasswordImpl.java b/password/impl/src/main/java/org/wildfly/security/password/impl/UnixDESCryptPasswordImpl.java
index 00ad94110da..267dfd70200 100644
--- a/password/impl/src/main/java/org/wildfly/security/password/impl/UnixDESCryptPasswordImpl.java
+++ b/password/impl/src/main/java/org/wildfly/security/password/impl/UnixDESCryptPasswordImpl.java
@@ -31,7 +31,6 @@
import java.security.spec.InvalidParameterSpecException;
import java.security.spec.KeySpec;
import java.util.Arrays;
-import java.util.concurrent.ThreadLocalRandom;
import org.wildfly.security.password.interfaces.UnixDESCryptPassword;
import org.wildfly.security.password.spec.ClearPasswordSpec;
@@ -68,11 +67,11 @@ class UnixDESCryptPasswordImpl extends AbstractPasswordImpl implements UnixDESCr
}
UnixDESCryptPasswordImpl(final ClearPasswordSpec spec) throws InvalidKeySpecException, InvalidKeyException {
- this((short) (ThreadLocalRandom.current().nextInt() & 0xfff), spec.getEncodedPassword());
+ this((short) (PasswordUtil.generateRandomSaltInt() & 0xfff), spec.getEncodedPassword());
}
UnixDESCryptPasswordImpl(final char[] passwordChars, final Charset hashCharset) throws InvalidKeyException {
- this((short) (ThreadLocalRandom.current().nextInt() & 0xfff), passwordChars, hashCharset);
+ this((short) (PasswordUtil.generateRandomSaltInt() & 0xfff), passwordChars, hashCharset);
}
UnixDESCryptPasswordImpl(final char[] passwordChars, SaltedPasswordAlgorithmSpec algorithmSpec, final Charset hashCharset) throws InvalidParameterSpecException, InvalidKeyException {
diff --git a/password/impl/src/test/java/org/wildfly/security/password/impl/PasswordUtilTest.java b/password/impl/src/test/java/org/wildfly/security/password/impl/PasswordUtilTest.java
new file mode 100644
index 00000000000..ace17e04677
--- /dev/null
+++ b/password/impl/src/test/java/org/wildfly/security/password/impl/PasswordUtilTest.java
@@ -0,0 +1,32 @@
+/*
+ * JBoss, Home of Professional Open Source
+ *
+ * Copyright 2024 Red Hat, Inc. and/or its affiliates.
+ *
+ * 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.password.impl;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class PasswordUtilTest {
+
+ @Test
+ public void testConvertBytesToInt() {
+ Assert.assertEquals(0, PasswordUtil.convertBytesToInt(new byte[] {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00}));
+ Assert.assertEquals(Integer.MAX_VALUE, PasswordUtil.convertBytesToInt(new byte[] {(byte) 0x7f, (byte) 0xff, (byte) 0xff, (byte) 0xff}));
+ Assert.assertEquals(Integer.MIN_VALUE, PasswordUtil.convertBytesToInt(new byte[] {(byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00}));
+ Assert.assertEquals(-1, PasswordUtil.convertBytesToInt(new byte[] {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff}));
+ }
+}
diff --git a/password/impl/src/test/java/org/wildfly/security/password/impl/SunUnixMD5CryptTest.java b/password/impl/src/test/java/org/wildfly/security/password/impl/SunUnixMD5CryptTest.java
index 53306441384..b7a306f85e0 100644
--- a/password/impl/src/test/java/org/wildfly/security/password/impl/SunUnixMD5CryptTest.java
+++ b/password/impl/src/test/java/org/wildfly/security/password/impl/SunUnixMD5CryptTest.java
@@ -24,7 +24,6 @@
import static org.junit.Assert.assertTrue;
import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import org.junit.Test;
@@ -43,42 +42,31 @@
public class SunUnixMD5CryptTest {
@Test
- public void testParseCryptStringWithoutRounds() throws NoSuchAlgorithmException, InvalidKeySpecException {
- String cryptString = "$md5$zrdhpMlZ$$wBvMOEqbSjU.hu5T2VEP01";
-
- // Get the spec by parsing the crypt string
- SunUnixMD5CryptPassword password = (SunUnixMD5CryptPassword) ModularCrypt.decode(cryptString);
- assertEquals(0, password.getIterationCount());
-
- // Use the spec to build a new crypt string and compare it to the original
- assertEquals(cryptString, ModularCrypt.encodeAsString(password));
+ public void testParseCryptStringWithoutRounds() throws InvalidKeySpecException {
+ testParseCryptString("$md5$zrdhpMlZ$$wBvMOEqbSjU.hu5T2VEP01", 0);
}
@Test
- public void testParseCryptStringWithRounds() throws NoSuchAlgorithmException, InvalidKeySpecException {
- String cryptString = "$md5,rounds=1000$saltstring$$1wGsmnKgDGdu03LxKu0VI1";
-
- // Get the spec by parsing the crypt string
- SunUnixMD5CryptPassword password = (SunUnixMD5CryptPassword) ModularCrypt.decode(cryptString);
- assertEquals(1_000, password.getIterationCount());
-
- // Use the spec to build a new crypt string and compare it to the original
- assertEquals(cryptString, ModularCrypt.encodeAsString(password));
+ public void testParseCryptStringWithRounds() throws InvalidKeySpecException {
+ testParseCryptString("$md5,rounds=1000$saltstring$$1wGsmnKgDGdu03LxKu0VI1", 1_000);
}
@Test
- public void testParseCryptStringWithBareSalt() throws NoSuchAlgorithmException, InvalidKeySpecException {
- String cryptString = "$md5,rounds=1500$saltstring$F9DNxgHVXWaeLS9zUaWXd.";
+ public void testParseCryptStringWithBareSalt() throws InvalidKeySpecException {
+ testParseCryptString("$md5,rounds=1500$saltstring$F9DNxgHVXWaeLS9zUaWXd.", 1_500);
+ }
- // Get the spec by parsing the crypt string
+ private static void testParseCryptString(String cryptString, int iterCount) throws InvalidKeySpecException {
SunUnixMD5CryptPassword password = (SunUnixMD5CryptPassword) ModularCrypt.decode(cryptString);
- assertEquals(1_500, password.getIterationCount());
+ assertEquals(iterCount, password.getIterationCount());
// Use the spec to build a new crypt string and compare it to the original
assertEquals(cryptString, ModularCrypt.encodeAsString(password));
+
}
- private void generateAndVerify(String cryptString, String correctPassword) throws NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException {
+
+ private static void generateAndVerify(String cryptString, String correctPassword) throws InvalidKeyException, InvalidKeySpecException {
final PasswordFactorySpiImpl spi = new PasswordFactorySpiImpl();
SunUnixMD5CryptPassword password = (SunUnixMD5CryptPassword) ModularCrypt.decode(cryptString);
final String algorithm = password.getAlgorithm();
@@ -100,49 +88,49 @@ private void generateAndVerify(String cryptString, String correctPassword) throw
}
@Test
- public void testHashEmptyPassword() throws NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException {
+ public void testHashEmptyPassword() throws InvalidKeyException, InvalidKeySpecException {
String password = "";
String cryptString = "$md5,rounds=10000$saltstring$$uwcsteApj7mCi4AIwYIT5.";
generateAndVerify(cryptString, password);
}
@Test
- public void testHashEmptyPasswordWithBareSalt() throws NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException {
+ public void testHashEmptyPasswordWithBareSalt() throws InvalidKeyException, InvalidKeySpecException {
String password = "";
String cryptString = "$md5,rounds=10000$saltstring$gWOS3RRZtQ5TiYRg.vBx40";
generateAndVerify(cryptString, password);
}
@Test
- public void testHashShortPassword() throws NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException {
+ public void testHashShortPassword() throws InvalidKeyException, InvalidKeySpecException {
String password = "Hello world!";
String cryptString = "$md5$saltstringsalt$$MsEJKkfiaflU4ioBHkqWe0";
generateAndVerify(cryptString, password);
}
@Test
- public void testHashShortPasswordWithBareSalt() throws NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException {
+ public void testHashShortPasswordWithBareSalt() throws InvalidKeyException, InvalidKeySpecException {
String password = "Hello world!";
String cryptString = "$md5$saltstringsalt$uOXM5LLS7ZtN3eYYS54sM/";
generateAndVerify(cryptString, password);
}
@Test
- public void testHashLongPassword() throws NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException {
+ public void testHashLongPassword() throws InvalidKeyException, InvalidKeySpecException {
String password = "This is a very very very long password! This is the 2nd sentence in THE password. This is a test.@$%";
String cryptString = "$md5,rounds=10000$saltstringsaltstring$$Occfaf7BttKIkRRUARiWU0";
generateAndVerify(cryptString, password);
}
@Test
- public void testHashLongPasswordWithBareSalt() throws NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException {
+ public void testHashLongPasswordWithBareSalt() throws InvalidKeyException, InvalidKeySpecException {
String password = "This is a very very very long password! This is the 2nd sentence in THE password. This is a test.@$%";
String cryptString = "$md5,rounds=10000$saltstringsaltstring$0xbVBdJfPIual8oRvkU/f.";
generateAndVerify(cryptString, password);
}
@Test
- public void testKnownCryptStrings() throws NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException {
+ public void testKnownCryptStrings() throws InvalidKeyException, InvalidKeySpecException {
// Crypt string with bare salt
generateAndVerify("$md5$RPgLF6IJ$WTvAlUJ7MqH5xak2FMEwS/", "passwd");
diff --git a/permission/pom.xml b/permission/pom.xml
index d089d44f12d..de8707d9c43 100644
--- a/permission/pom.xml
+++ b/permission/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
4.0.0
diff --git a/permission/src/main/java/org/wildfly/security/permission/IntNameSetPermissionCollection.java b/permission/src/main/java/org/wildfly/security/permission/IntNameSetPermissionCollection.java
index 81653a35ae2..1174e427149 100644
--- a/permission/src/main/java/org/wildfly/security/permission/IntNameSetPermissionCollection.java
+++ b/permission/src/main/java/org/wildfly/security/permission/IntNameSetPermissionCollection.java
@@ -33,10 +33,6 @@ final class IntNameSetPermissionCollection extends NameSetPermissionCollection {
super(sourcePermission, nameEnumeration);
}
- private Permission permissionFor(int id) {
- return ((AbstractNamedPermission>)getSourcePermission()).withName(getNameEnumeration().nameOf(id));
- }
-
protected void doAdd(final AbstractPermission> permission) {
int setBits= getBitsForName(permission);
final AtomicInteger bitSet = this.bitSet;
@@ -113,6 +109,10 @@ public boolean hasNext() {
public Permission next() {
return nextElement();
}
+
+ private Permission permissionFor(int id) {
+ return ((AbstractNamedPermission>)getSourcePermission()).withName(getNameEnumeration().nameOf(id));
+ }
}
}
diff --git a/permission/src/main/java/org/wildfly/security/permission/LongNameSetPermissionCollection.java b/permission/src/main/java/org/wildfly/security/permission/LongNameSetPermissionCollection.java
index 3ef54a65549..4ebc3469911 100644
--- a/permission/src/main/java/org/wildfly/security/permission/LongNameSetPermissionCollection.java
+++ b/permission/src/main/java/org/wildfly/security/permission/LongNameSetPermissionCollection.java
@@ -33,10 +33,6 @@ final class LongNameSetPermissionCollection extends NameSetPermissionCollection
super(sourcePermission, nameEnumeration);
}
- private Permission permissionFor(int id) {
- return ((AbstractNamedPermission>)getSourcePermission()).withName(getNameEnumeration().nameOf(id));
- }
-
protected void doAdd(final AbstractPermission> permission) {
long setBits = getBitsForName(permission);
final AtomicLong bitSet = this.bitSet;
@@ -113,6 +109,10 @@ public boolean hasNext() {
public Permission next() {
return nextElement();
}
+
+ private Permission permissionFor(int id) {
+ return ((AbstractNamedPermission>)getSourcePermission()).withName(getNameEnumeration().nameOf(id));
+ }
}
}
diff --git a/permission/src/main/java/org/wildfly/security/permission/PermissionActions.java b/permission/src/main/java/org/wildfly/security/permission/PermissionActions.java
index a45c5fdad88..f4aa390da7c 100644
--- a/permission/src/main/java/org/wildfly/security/permission/PermissionActions.java
+++ b/permission/src/main/java/org/wildfly/security/permission/PermissionActions.java
@@ -38,7 +38,7 @@
*
* @author David M. Lloyd
*
- * @deprecated Use one of the abstract permission classes like {@link AbstractActionSetPermission} instead.
+ * @deprecated Use one of the abstract permission classes like {@link org.wildfly.security.permission.AbstractActionSetPermission org.wildfly.security.permission.AbstractActionSetPermission} instead.
*/
@Deprecated
public final class PermissionActions {
diff --git a/pom.xml b/pom.xml
index 2978619fac8..1c3d9b88295 100644
--- a/pom.xml
+++ b/pom.xml
@@ -31,7 +31,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
pom
WildFly Elytron Parent
@@ -56,48 +56,53 @@
11
- 2.15.2
+ 3.7.0
+ 2.17.0
${version.com.fasterxml.jackson}
- 1.4
- 2.0.2
- 3.8.1
- 2.0.0-M24
- 1.0.0
+ 1.6.0
+ 4.0.1
+ 3.15.0
+ 2.0.0.AM27
+ 2.1.6
+ 2.0.3
2.0.0-M3
1.0.0-M8
1.67
- 2.9.2
- 4.5.13
- 4.4.15
- 3.4.3.Final
- 2.1.18.Final
+ 2.12.0
+ 4.5.14
+ 4.4.16
+ 1.1.5
+ 3.5.3.Final
+ 2.1.19.Final
1.1.6.Final
2.2.1.Final
- 1.9.2.Final
- 1.0.4.GA
- 2.0.0
+ 1.12.2.Final
+ 1.2.0.Final
+ 2.1.2
5.0.0
- 2.4.0.Final
- 1.7
+ 3.5.1.Final
+ 1.11
4.13.1
1.34
- 2.4.0
- 2.0.0
+ 2.7.1
2.4.9
- 8.2.1
+ 9.37.3
3.8.1
1.0.8.Final
1.0.1.Final
- 1.5.4.Final
+ 1.6.0.Final
2.7
5.4.1
3.0.0
- 0.9.3
+ 0.9.6
1.15.3
- 18.0.2
- 4.3.3
+ 25.0.2
+ 5.5.0
2.40.0
2.3.0
+ 23.0.7
+ 2.7
+ 3.8.16.Final
INFO
@@ -253,11 +258,119 @@
maven-javadoc-plugin
+ ${version-javadoc-plugin}
+
+
+ org.wildfly.common
+ wildfly-common
+ ${version.org.wildfly.common}
+
+
+ org.jboss.logging
+ jboss-logging
+ ${version.org.jboss.logging}
+
+
+ org.jboss.logging
+ jboss-logging-annotations
+ ${version.org.jboss.logging.tools}
+
+
+ org.jboss.logmanager
+ jboss-logmanager
+ ${version.org.jboss.logmanager}
+
+
+ org.wildfly.security
+ wildfly-elytron-credential-source-impl
+ ${project.version}
+
+
+ org.wildfly.security
+ wildfly-elytron-ssh-util
+ ${project.version}
+
+
+ org.wildfly.client
+ wildfly-client-config
+ ${version.org.wildfly.client.config}
+
+
+ org.wildfly.security
+ wildfly-elytron-provider-util
+ ${project.version}
+
+
+ org.kohsuke.metainf-services
+ metainf-services
+ ${version.org.kohsuke.metainf-services.metainf-services}
+
+
+ org.apache.httpcomponents
+ httpcore
+ ${version.org.apache.httpcomponents.httpcore}
+
+
+ org.apache.httpcomponents
+ httpclient
+ ${version.org.apache.httpcomponents.httpclient}
+
+
+ org.bitbucket.b_c
+ jose4j
+ ${version.org.bitbucket.b_c.jose4j}
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ ${version.com.fasterxml.jackson.databind}
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+ ${version.com.fasterxml.jackson}
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+ ${version.com.fasterxml.jackson}
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+ ${version.jakarta.servlet.jakarta-servlet-api}
+
+
+ jakarta.json
+ jakarta.json-api
+ ${version.jakarta.json.jakarta-json-api}
+
+
+ commons-cli
+ commons-cli
+ ${version.commons-cli}
+
+
+ org.apache.commons
+ commons-lang3
+ ${version.org.apache.commons}
+
+
+ org.apache.sshd
+ sshd-common
+ ${version.org.apache.sshd.common}
+
+
+ org.jboss.modules
+ jboss-modules
+ ${version.org.jboss.modules}
+
+
true
none
protected
- 8
+ 11
${project.basedir}/asn1/src/main/java/;
${project.basedir}/audit/src/main/java/;
@@ -521,6 +634,11 @@
wildfly-elytron-digest
${project.version}
+
+ org.wildfly.security
+ wildfly-elytron-dynamic-ssl
+ ${project.version}
+
org.wildfly.security
wildfly-elytron-encryption
@@ -994,6 +1112,11 @@
jose4j
${version.org.bitbucket.b_c.jose4j}
+
+ org.aesh
+ aesh
+ ${version.org.aesh}
+
@@ -1156,9 +1285,9 @@
- org.glassfish
+ org.eclipse.parsson
jakarta.json
- ${version.org.glassfish.jakarta.json}
+ ${version.org.eclipse.parsson.jakarta.json}
test
@@ -1381,6 +1510,7 @@
credential/source/impl
digest
encryption
+ dynamic-ssl
http/base
http/basic
http/bearer
diff --git a/provider/util/pom.xml b/provider/util/pom.xml
index dba50f1d05e..75d25211809 100644
--- a/provider/util/pom.xml
+++ b/provider/util/pom.xml
@@ -23,7 +23,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/sasl/anonymous/pom.xml b/sasl/anonymous/pom.xml
index 81d019db7f7..187cd392425 100644
--- a/sasl/anonymous/pom.xml
+++ b/sasl/anonymous/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/sasl/auth/util/pom.xml b/sasl/auth/util/pom.xml
index 1c76594a76c..b3464240776 100644
--- a/sasl/auth/util/pom.xml
+++ b/sasl/auth/util/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../../pom.xml
diff --git a/sasl/base/pom.xml b/sasl/base/pom.xml
index d54737a9f17..c69df220d9f 100644
--- a/sasl/base/pom.xml
+++ b/sasl/base/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/sasl/base/src/main/java/org/wildfly/security/sasl/util/SaslMechanismInformation.java b/sasl/base/src/main/java/org/wildfly/security/sasl/util/SaslMechanismInformation.java
index 7eb7361b639..62a8d01aad2 100644
--- a/sasl/base/src/main/java/org/wildfly/security/sasl/util/SaslMechanismInformation.java
+++ b/sasl/base/src/main/java/org/wildfly/security/sasl/util/SaslMechanismInformation.java
@@ -55,6 +55,9 @@
*/
public final class SaslMechanismInformation {
+ /**
+ * The class providing Sasl Mechanism Names.
+ */
public static final class Names {
public static final String CRAM_MD5 = "CRAM-MD5";
public static final String DIGEST_MD5 = "DIGEST-MD5";
diff --git a/sasl/base/src/main/java/org/wildfly/security/sasl/util/UsernamePasswordHashUtil.java b/sasl/base/src/main/java/org/wildfly/security/sasl/util/UsernamePasswordHashUtil.java
index aa1a8e43403..d118dfbfb1e 100644
--- a/sasl/base/src/main/java/org/wildfly/security/sasl/util/UsernamePasswordHashUtil.java
+++ b/sasl/base/src/main/java/org/wildfly/security/sasl/util/UsernamePasswordHashUtil.java
@@ -34,7 +34,7 @@
*
* @author Darran Lofthouse
*
- * @deprecated Use {@link org.wildfly.security.password.PasswordFactory} instead.
+ * @deprecated Use {@link org.wildfly.security.password.PasswordFactory org.wildfly.security.password.PasswordFactory} instead.
*/
@Deprecated
public class UsernamePasswordHashUtil {
diff --git a/sasl/deprecated/pom.xml b/sasl/deprecated/pom.xml
index 9b8d3ecb715..670e9617d71 100644
--- a/sasl/deprecated/pom.xml
+++ b/sasl/deprecated/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/sasl/deprecated/src/main/java/org/wildfly/security/sasl/util/AuthenticationContextSaslClient.java b/sasl/deprecated/src/main/java/org/wildfly/security/sasl/util/AuthenticationContextSaslClient.java
index b7f54b4501f..6c98492af3f 100644
--- a/sasl/deprecated/src/main/java/org/wildfly/security/sasl/util/AuthenticationContextSaslClient.java
+++ b/sasl/deprecated/src/main/java/org/wildfly/security/sasl/util/AuthenticationContextSaslClient.java
@@ -28,7 +28,7 @@
* of the authentication process.
*
* @author David M. Lloyd
- * @deprecated Use {@link org.wildfly.security.sasl.auth.util.AuthenticationContextSaslClient} instead.
+ * @deprecated Use {@link org.wildfly.security.sasl.auth.util.AuthenticationContextSaslClient org.wildfly.security.sasl.auth.util.AuthenticationContextSaslClient} instead.
*/
@Deprecated
public final class AuthenticationContextSaslClient extends AbstractDelegatingSaslClient {
diff --git a/sasl/deprecated/src/main/java/org/wildfly/security/sasl/util/AuthenticationContextSaslClientFactory.java b/sasl/deprecated/src/main/java/org/wildfly/security/sasl/util/AuthenticationContextSaslClientFactory.java
index 9afa9d31cfb..4b4a4980a2b 100644
--- a/sasl/deprecated/src/main/java/org/wildfly/security/sasl/util/AuthenticationContextSaslClientFactory.java
+++ b/sasl/deprecated/src/main/java/org/wildfly/security/sasl/util/AuthenticationContextSaslClientFactory.java
@@ -33,7 +33,7 @@
* of the authentication process.
*
* @author David M. Lloyd
- * @deprecated Use {@link org.wildfly.security.sasl.auth.util.AuthenticationContextSaslClientFactory} instead.
+ * @deprecated Use {@link org.wildfly.security.sasl.auth.util.AuthenticationContextSaslClientFactory org.wildfly.security.sasl.auth.util.AuthenticationContextSaslClientFactory} instead.
*/
@Deprecated
public final class AuthenticationContextSaslClientFactory extends AbstractDelegatingSaslClientFactory {
diff --git a/sasl/deprecated/src/main/java/org/wildfly/security/sasl/util/AuthenticationContextSaslServer.java b/sasl/deprecated/src/main/java/org/wildfly/security/sasl/util/AuthenticationContextSaslServer.java
index 0ad16526b80..9daf5422ac2 100644
--- a/sasl/deprecated/src/main/java/org/wildfly/security/sasl/util/AuthenticationContextSaslServer.java
+++ b/sasl/deprecated/src/main/java/org/wildfly/security/sasl/util/AuthenticationContextSaslServer.java
@@ -29,7 +29,7 @@
* of the authentication process.
*
* @author David M. Lloyd
- * @deprecated Use {@link org.wildfly.security.sasl.auth.util.AuthenticationContextSaslServer} instead.
+ * @deprecated Use {@link org.wildfly.security.sasl.auth.util.AuthenticationContextSaslServer org.wildfly.security.sasl.auth.util.AuthenticationContextSaslServer} instead.
*/
@Deprecated
public final class AuthenticationContextSaslServer extends AbstractDelegatingSaslServer {
diff --git a/sasl/deprecated/src/main/java/org/wildfly/security/sasl/util/AuthenticationContextSaslServerFactory.java b/sasl/deprecated/src/main/java/org/wildfly/security/sasl/util/AuthenticationContextSaslServerFactory.java
index 4c567577b51..e3eb37f4972 100644
--- a/sasl/deprecated/src/main/java/org/wildfly/security/sasl/util/AuthenticationContextSaslServerFactory.java
+++ b/sasl/deprecated/src/main/java/org/wildfly/security/sasl/util/AuthenticationContextSaslServerFactory.java
@@ -32,7 +32,7 @@
* of the authentication process.
*
* @author David M. Lloyd
- * @deprecated Use {@link org.wildfly.security.sasl.auth.util.AuthenticationContextSaslServerFactory} instead.
+ * @deprecated Use {@link org.wildfly.security.sasl.auth.util.AuthenticationContextSaslServerFactory org.wildfly.security.sasl.auth.util.AuthenticationContextSaslServerFactory} instead.
*/
@Deprecated
public final class AuthenticationContextSaslServerFactory extends AbstractDelegatingSaslServerFactory {
diff --git a/sasl/digest/pom.xml b/sasl/digest/pom.xml
index 7dcb935ce6d..9b8ab397d77 100644
--- a/sasl/digest/pom.xml
+++ b/sasl/digest/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/sasl/digest/src/main/java/org/wildfly/security/sasl/digest/AbstractDigestMechanism.java b/sasl/digest/src/main/java/org/wildfly/security/sasl/digest/AbstractDigestMechanism.java
index 7a128580188..61b05cf0aa6 100644
--- a/sasl/digest/src/main/java/org/wildfly/security/sasl/digest/AbstractDigestMechanism.java
+++ b/sasl/digest/src/main/java/org/wildfly/security/sasl/digest/AbstractDigestMechanism.java
@@ -73,6 +73,8 @@ public enum FORMAT {CLIENT, SERVER}
public static final int DEFAULT_MAXBUF = 65536;
public static final char DELIMITER = ',';
public static final String[] CIPHER_OPTS = {"des", "3des", "rc4", "rc4-40", "rc4-56"};
+ private static final String CLIENT_MAGIC_INTEGRITY = "Digest session key to client-to-server signing key magic constant";
+ private static final String SERVER_MAGIC_INTEGRITY = "Digest session key to server-to-client signing key magic constant";
private FORMAT format;
protected final String digestURI;
@@ -217,9 +219,9 @@ protected DigestWrapper(boolean confidential) {
@Override
public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException {
if (confidential) {
- return AbstractDigestMechanism.this.wrapConfidentialityProtectedMessage(outgoing, offset, len);
+ return wrapConfidentialityProtectedMessage(outgoing, offset, len);
} else {
- return AbstractDigestMechanism.this.wrapIntegrityProtectedMessage(outgoing, offset, len);
+ return wrapIntegrityProtectedMessage(outgoing, offset, len);
}
}
@@ -229,152 +231,148 @@ public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException {
@Override
public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException {
if (confidential) {
- return AbstractDigestMechanism.this.unwrapConfidentialityProtectedMessage(incoming, offset, len);
+ return unwrapConfidentialityProtectedMessage(incoming, offset, len);
} else {
- return AbstractDigestMechanism.this.unwrapIntegrityProtectedMessage(incoming, offset, len);
+ return unwrapIntegrityProtectedMessage(incoming, offset, len);
}
}
+ private byte[] wrapIntegrityProtectedMessage(byte[] message, int offset, int len) throws SaslException {
- }
-
- private static final String CLIENT_MAGIC_INTEGRITY = "Digest session key to client-to-server signing key magic constant";
- private static final String SERVER_MAGIC_INTEGRITY = "Digest session key to server-to-client signing key magic constant";
+ byte[] messageMac = computeHMAC(wrapHmacKeyIntegrity, wrapSeqNum, hmacMD5, message, offset, len);
- private byte[] wrapIntegrityProtectedMessage(byte[] message, int offset, int len) throws SaslException {
-
- byte[] messageMac = computeHMAC(wrapHmacKeyIntegrity, wrapSeqNum, hmacMD5, message, offset, len);
+ byte[] result = new byte[len + 16];
+ System.arraycopy(message, offset, result, 0, len);
+ System.arraycopy(messageMac, 0, result, len, 10);
+ integerByteOrdered(1, result, len + 10, 2); // 2-byte message type number in network byte order with value 1
+ integerByteOrdered(wrapSeqNum, result, len + 12, 4); // 4-byte sequence number in network byte order
+ wrapSeqNum++;
+ return result;
+ }
- byte[] result = new byte[len + 16];
- System.arraycopy(message, offset, result, 0, len);
- System.arraycopy(messageMac, 0, result, len, 10);
- integerByteOrdered(1, result, len + 10, 2); // 2-byte message type number in network byte order with value 1
- integerByteOrdered(wrapSeqNum, result, len + 12, 4); // 4-byte sequence number in network byte order
- wrapSeqNum++;
- return result;
- }
+ private byte[] unwrapIntegrityProtectedMessage(byte[] message, int offset, int len) throws SaslException {
- private byte[] unwrapIntegrityProtectedMessage(byte[] message, int offset, int len) throws SaslException {
+ int messageType = decodeByteOrderedInteger(message, offset + len - 6, 2);
+ int extractedSeqNum = decodeByteOrderedInteger(message, offset + len - 4, 4);
- int messageType = decodeByteOrderedInteger(message, offset + len - 6, 2);
- int extractedSeqNum = decodeByteOrderedInteger(message, offset + len - 4, 4);
+ if (messageType != 1) {
+ throw saslDigest.mechMessageTypeMustEqual(1, messageType).toSaslException();
+ }
- if (messageType != 1) {
- throw saslDigest.mechMessageTypeMustEqual(1, messageType).toSaslException();
- }
+ if (extractedSeqNum != unwrapSeqNum) {
+ throw saslDigest.mechBadSequenceNumberWhileUnwrapping(unwrapSeqNum, extractedSeqNum).toSaslException();
+ }
- if (extractedSeqNum != unwrapSeqNum) {
- throw saslDigest.mechBadSequenceNumberWhileUnwrapping(unwrapSeqNum, extractedSeqNum).toSaslException();
- }
+ byte[] extractedMessageMac = new byte[10];
+ byte[] extractedMessage = new byte[len - 16];
+ System.arraycopy(message, offset, extractedMessage, 0, len - 16);
+ System.arraycopy(message, offset + len - 16, extractedMessageMac, 0, 10);
- byte[] extractedMessageMac = new byte[10];
- byte[] extractedMessage = new byte[len - 16];
- System.arraycopy(message, offset, extractedMessage, 0, len - 16);
- System.arraycopy(message, offset + len - 16, extractedMessageMac, 0, 10);
+ byte[] expectedHmac = computeHMAC(unwrapHmacKeyIntegrity, extractedSeqNum, hmacMD5, extractedMessage, 0, extractedMessage.length);
- byte[] expectedHmac = computeHMAC(unwrapHmacKeyIntegrity, extractedSeqNum, hmacMD5, extractedMessage, 0, extractedMessage.length);
+ // validate MAC block
+ if (Arrays2.equals(expectedHmac, 0, extractedMessageMac, 0, 10) == false) {
+ return NO_BYTES;
+ }
- // validate MAC block
- if (Arrays2.equals(expectedHmac, 0, extractedMessageMac, 0, 10) == false) {
- return NO_BYTES;
+ unwrapSeqNum++; // increment only if MAC is valid
+ return extractedMessage;
}
- unwrapSeqNum++; // increment only if MAC is valid
- return extractedMessage;
- }
-
- private byte[] wrapConfidentialityProtectedMessage(byte[] message, int offset, int len) throws SaslException {
+ private byte[] wrapConfidentialityProtectedMessage(byte[] message, int offset, int len) throws SaslException {
- byte[] messageMac = computeHMAC(wrapHmacKeyIntegrity, wrapSeqNum, hmacMD5, message, offset, len);
+ byte[] messageMac = computeHMAC(wrapHmacKeyIntegrity, wrapSeqNum, hmacMD5, message, offset, len);
- int paddingLength = 0;
- byte[] pad = null;
- int blockSize = wrapCipher.getBlockSize();
- if (blockSize > 0) {
- paddingLength = blockSize - ((len + 10) % blockSize);
- pad = new byte[paddingLength];
- Arrays.fill(pad, (byte)paddingLength);
- }
+ int paddingLength = 0;
+ byte[] pad = null;
+ int blockSize = wrapCipher.getBlockSize();
+ if (blockSize > 0) {
+ paddingLength = blockSize - ((len + 10) % blockSize);
+ pad = new byte[paddingLength];
+ Arrays.fill(pad, (byte)paddingLength);
+ }
- byte[] toCipher = new byte[len + paddingLength + 10];
- System.arraycopy(message, offset, toCipher, 0, len);
- if (paddingLength > 0) {
- System.arraycopy(pad, 0, toCipher, len, paddingLength);
- }
- System.arraycopy(messageMac, 0, toCipher, len + paddingLength, 10);
+ byte[] toCipher = new byte[len + paddingLength + 10];
+ System.arraycopy(message, offset, toCipher, 0, len);
+ if (paddingLength > 0) {
+ System.arraycopy(pad, 0, toCipher, len, paddingLength);
+ }
+ System.arraycopy(messageMac, 0, toCipher, len + paddingLength, 10);
- byte[] cipheredPart = null;
- try {
- cipheredPart = wrapCipher.update(toCipher);
- } catch (Exception e) {
- throw saslDigest.mechProblemDuringCrypt(e).toSaslException();
- }
- if (cipheredPart == null){
- throw saslDigest.mechProblemDuringCryptResultIsNull().toSaslException();
- }
+ byte[] cipheredPart = null;
+ try {
+ cipheredPart = wrapCipher.update(toCipher);
+ } catch (Exception e) {
+ throw saslDigest.mechProblemDuringCrypt(e).toSaslException();
+ }
+ if (cipheredPart == null){
+ throw saslDigest.mechProblemDuringCryptResultIsNull().toSaslException();
+ }
- byte[] result = new byte[cipheredPart.length + 6];
- System.arraycopy(cipheredPart, 0, result, 0, cipheredPart.length);
- integerByteOrdered(1, result, cipheredPart.length, 2); // 2-byte message type number in network byte order with value 1
- integerByteOrdered(wrapSeqNum, result, cipheredPart.length + 2, 4); // 4-byte sequence number in network byte order
+ byte[] result = new byte[cipheredPart.length + 6];
+ System.arraycopy(cipheredPart, 0, result, 0, cipheredPart.length);
+ integerByteOrdered(1, result, cipheredPart.length, 2); // 2-byte message type number in network byte order with value 1
+ integerByteOrdered(wrapSeqNum, result, cipheredPart.length + 2, 4); // 4-byte sequence number in network byte order
- wrapSeqNum++;
- return result;
- }
+ wrapSeqNum++;
+ return result;
+ }
- private byte[] unwrapConfidentialityProtectedMessage(byte[] message, int offset, int len) throws SaslException {
+ private byte[] unwrapConfidentialityProtectedMessage(byte[] message, int offset, int len) throws SaslException {
- int messageType = decodeByteOrderedInteger(message, offset + len - 6, 2);
- int extractedSeqNum = decodeByteOrderedInteger(message, offset + len - 4, 4);
+ int messageType = decodeByteOrderedInteger(message, offset + len - 6, 2);
+ int extractedSeqNum = decodeByteOrderedInteger(message, offset + len - 4, 4);
- if (messageType != 1) {
- throw saslDigest.mechMessageTypeMustEqual(1, messageType).toSaslException();
- }
+ if (messageType != 1) {
+ throw saslDigest.mechMessageTypeMustEqual(1, messageType).toSaslException();
+ }
- if (extractedSeqNum != unwrapSeqNum) {
- throw saslDigest.mechBadSequenceNumberWhileUnwrapping(unwrapSeqNum, extractedSeqNum).toSaslException();
- }
+ if (extractedSeqNum != unwrapSeqNum) {
+ throw saslDigest.mechBadSequenceNumberWhileUnwrapping(unwrapSeqNum, extractedSeqNum).toSaslException();
+ }
- byte[] clearText = null;
- try {
- clearText = unwrapCipher.update(message, offset, len - 6);
- } catch (Exception e) {
- throw saslDigest.mechProblemDuringDecrypt(e).toSaslException();
- }
- if (clearText == null){
- throw saslDigest.mechProblemDuringDecryptResultIsNull().toSaslException();
- }
+ byte[] clearText = null;
+ try {
+ clearText = unwrapCipher.update(message, offset, len - 6);
+ } catch (Exception e) {
+ throw saslDigest.mechProblemDuringDecrypt(e).toSaslException();
+ }
+ if (clearText == null){
+ throw saslDigest.mechProblemDuringDecryptResultIsNull().toSaslException();
+ }
- byte[] hmac = new byte[10];
- System.arraycopy(clearText, clearText.length - 10, hmac, 0, 10);
-
- byte[] decryptedMessage = null;
- // strip potential padding
- if (unwrapCipher.getBlockSize() > 0) {
- int padSize = clearText[clearText.length - 10 - 1];
- int decryptedMessageSize = clearText.length - 10;
- if (padSize < 8) {
- int i = clearText.length - 10 - 1;
- while (clearText[i] == padSize) {
- i--;
+ byte[] hmac = new byte[10];
+ System.arraycopy(clearText, clearText.length - 10, hmac, 0, 10);
+
+ byte[] decryptedMessage = null;
+ // strip potential padding
+ if (unwrapCipher.getBlockSize() > 0) {
+ int padSize = clearText[clearText.length - 10 - 1];
+ int decryptedMessageSize = clearText.length - 10;
+ if (padSize < 8) {
+ int i = clearText.length - 10 - 1;
+ while (clearText[i] == padSize) {
+ i--;
+ }
+ decryptedMessageSize = i + 1;
}
- decryptedMessageSize = i + 1;
+ decryptedMessage = new byte[decryptedMessageSize];
+ System.arraycopy(clearText, 0, decryptedMessage, 0, decryptedMessageSize);
+ } else {
+ decryptedMessage = new byte[clearText.length - 10];
+ System.arraycopy(clearText, 0, decryptedMessage, 0, clearText.length - 10);
}
- decryptedMessage = new byte[decryptedMessageSize];
- System.arraycopy(clearText, 0, decryptedMessage, 0, decryptedMessageSize);
- } else {
- decryptedMessage = new byte[clearText.length - 10];
- System.arraycopy(clearText, 0, decryptedMessage, 0, clearText.length - 10);
- }
- byte[] expectedHmac = computeHMAC(unwrapHmacKeyIntegrity, extractedSeqNum, hmacMD5, decryptedMessage, 0, decryptedMessage.length);
+ byte[] expectedHmac = computeHMAC(unwrapHmacKeyIntegrity, extractedSeqNum, hmacMD5, decryptedMessage, 0, decryptedMessage.length);
+
+ // check hmac-s
+ if (Arrays2.equals(expectedHmac, 0, hmac, 0, 10) == false) {
+ return NO_BYTES;
+ }
- // check hmac-s
- if (Arrays2.equals(expectedHmac, 0, hmac, 0, 10) == false) {
- return NO_BYTES;
+ unwrapSeqNum++; // increment only if MAC is valid
+ return decryptedMessage;
}
- unwrapSeqNum++; // increment only if MAC is valid
- return decryptedMessage;
}
protected void createCiphersAndKeys() throws SaslException {
diff --git a/sasl/entity/pom.xml b/sasl/entity/pom.xml
index c54dcf9ef1b..641fc593cb2 100644
--- a/sasl/entity/pom.xml
+++ b/sasl/entity/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/sasl/external/pom.xml b/sasl/external/pom.xml
index ff509270cc2..519e17253d6 100644
--- a/sasl/external/pom.xml
+++ b/sasl/external/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/sasl/external/src/test/java/org/wildfly/security/sasl/external/ExternalSaslClientTest.java b/sasl/external/src/test/java/org/wildfly/security/sasl/external/ExternalSaslClientTest.java
index 252114d8791..5d099418c40 100644
--- a/sasl/external/src/test/java/org/wildfly/security/sasl/external/ExternalSaslClientTest.java
+++ b/sasl/external/src/test/java/org/wildfly/security/sasl/external/ExternalSaslClientTest.java
@@ -40,6 +40,7 @@
import javax.security.sasl.SaslException;
import org.junit.AfterClass;
+import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.wildfly.security.sasl.WildFlySasl;
@@ -213,13 +214,19 @@ public void testServerChallengeEmptyAuthzId() throws Exception {
/**
* Test failing (as we only authenticate "admin") authn for unsupported data "test" from client.
*/
- @Test(expected = SaslException.class)
- public void testWrongServerChallenge() throws Exception {
+ @Test
+ public void testWrongServerChallenge() {
final SaslClientFactory factory = obtainSaslClientFactory(ExternalSaslClientFactory.class);
- final SaslClient saslClient = factory.createSaslClient(MECHANISMS_EXTERNAL_ONLY, ADMIN, "test", "localhost", setProps(),
- null);
- assertFalse(saslClient.isComplete());
- saslClient.evaluateChallenge("test".getBytes(StandardCharsets.UTF_8));
+ try {
+ final SaslClient saslClient = factory.createSaslClient(MECHANISMS_EXTERNAL_ONLY, ADMIN, "test", "localhost", setProps(),
+ null);
+ assertFalse(saslClient.isComplete());
+ Assert.assertThrows(SaslException.class,()->{
+ saslClient.evaluateChallenge("test".getBytes(StandardCharsets.UTF_8));
+ });
+ }catch(SaslException saslException){
+ fail("Failed to create SaslClient Instance");
+ }
}
@Test
diff --git a/sasl/external/src/test/java/org/wildfly/security/sasl/external/ExternalSaslServerTest.java b/sasl/external/src/test/java/org/wildfly/security/sasl/external/ExternalSaslServerTest.java
index 9a0e4e73c81..06b1a4af1d4 100644
--- a/sasl/external/src/test/java/org/wildfly/security/sasl/external/ExternalSaslServerTest.java
+++ b/sasl/external/src/test/java/org/wildfly/security/sasl/external/ExternalSaslServerTest.java
@@ -44,6 +44,7 @@
import javax.security.sasl.SaslServerFactory;
import org.junit.AfterClass;
+import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.wildfly.security.auth.callback.AuthenticationConfigurationCallback;
@@ -183,25 +184,36 @@ public void testAuthnClientData() throws Exception {
/**
* Test failing (as we only authenticate "admin") authn for unsupported data "test" from client.
*/
- @Test(expected = SaslException.class)
- public void testFailedAuthn() throws Exception {
- SaslServer saslServer = obtainSaslServerFactory(ExternalSaslServerFactory.class).createSaslServer(EXTERNAL, "test",
- "localhost", setProps(), CALLBACK_HANDLER_AUTHZ_ADMIN);
- assertFalse(saslServer.isComplete());
- saslServer.evaluateResponse("test".getBytes(StandardCharsets.UTF_8));
+ @Test
+ public void testFailedAuthn() {
+ try {
+ SaslServer saslServer = obtainSaslServerFactory(ExternalSaslServerFactory.class).createSaslServer(EXTERNAL, "test",
+ "localhost", setProps(), CALLBACK_HANDLER_AUTHZ_ADMIN);
+ assertFalse(saslServer.isComplete());
+ Assert.assertThrows(SaslException.class,()->{
+ saslServer.evaluateResponse("test".getBytes(StandardCharsets.UTF_8));
+ });
+ }catch (SaslException saslException){
+ fail("Failed to create SaslServer Instance");
+ }
}
/**
* Test failing authn (as we only authenticate "admin") for empty data received from client.
*/
- @Test(expected = SaslException.class)
- public void testAuthnEmptyData() throws Exception {
- SaslServer saslServer = obtainSaslServerFactory(ExternalSaslServerFactory.class).createSaslServer(EXTERNAL, "test",
- "localhost", setProps(), CALLBACK_HANDLER_AUTHZ_ADMIN);
-
- assertFalse(saslServer.isComplete());
-
- saslServer.evaluateResponse(AbstractSaslParticipant.NO_BYTES);
+ @Test
+ public void testAuthnEmptyData() {
+ try{
+ SaslServer saslServer = obtainSaslServerFactory(ExternalSaslServerFactory.class).createSaslServer(EXTERNAL, "test",
+ "localhost", setProps(), CALLBACK_HANDLER_AUTHZ_ADMIN);
+
+ assertFalse(saslServer.isComplete());
+ Assert.assertThrows(SaslException.class,()->{
+ saslServer.evaluateResponse(AbstractSaslParticipant.NO_BYTES);
+ });
+ }catch (SaslException saslException){
+ fail("Failed to create SaslServer Instance");
+ }
}
@Test
diff --git a/sasl/gs2/pom.xml b/sasl/gs2/pom.xml
index e9b525e3157..185464c86c0 100644
--- a/sasl/gs2/pom.xml
+++ b/sasl/gs2/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/sasl/gssapi/pom.xml b/sasl/gssapi/pom.xml
index 0a204def8d3..9e8c46f409e 100644
--- a/sasl/gssapi/pom.xml
+++ b/sasl/gssapi/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/sasl/localuser/pom.xml b/sasl/localuser/pom.xml
index aa8cfebf241..4d2dcb70815 100644
--- a/sasl/localuser/pom.xml
+++ b/sasl/localuser/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/sasl/oauth2/pom.xml b/sasl/oauth2/pom.xml
index 9981c7aa310..10585dc67e8 100644
--- a/sasl/oauth2/pom.xml
+++ b/sasl/oauth2/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/sasl/otp/pom.xml b/sasl/otp/pom.xml
index dc749ed6030..2ebcf826d96 100644
--- a/sasl/otp/pom.xml
+++ b/sasl/otp/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/sasl/plain/pom.xml b/sasl/plain/pom.xml
index da68c0f9155..ce0d479311a 100644
--- a/sasl/plain/pom.xml
+++ b/sasl/plain/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/sasl/scram/pom.xml b/sasl/scram/pom.xml
index c1699f121cb..6be4f3ec0aa 100644
--- a/sasl/scram/pom.xml
+++ b/sasl/scram/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/ssh/util/pom.xml b/ssh/util/pom.xml
index f606b2a8925..cfeab877b40 100644
--- a/ssh/util/pom.xml
+++ b/ssh/util/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/ssl/pom.xml b/ssl/pom.xml
index add987adf0d..bf3c3e3ebe1 100644
--- a/ssl/pom.xml
+++ b/ssl/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
4.0.0
diff --git a/ssl/src/main/java/org/wildfly/security/ssl/SNIContextMatcher.java b/ssl/src/main/java/org/wildfly/security/ssl/SNIContextMatcher.java
index 7790d2827ae..ff7aeceae5e 100644
--- a/ssl/src/main/java/org/wildfly/security/ssl/SNIContextMatcher.java
+++ b/ssl/src/main/java/org/wildfly/security/ssl/SNIContextMatcher.java
@@ -26,6 +26,9 @@
import javax.net.ssl.SNIServerName;
import javax.net.ssl.SSLContext;
+/**
+ * A class which returns a matching SSL context based on the SNI server list provided.
+ */
public class SNIContextMatcher {
private final SSLContext defaultContext;
@@ -38,6 +41,10 @@ public class SNIContextMatcher {
this.exacts = exacts;
}
+ /**
+ * Used for finding the matching servers from the server list.
+ * Firstly, the exacts are matched, if not found, wildcards are tried.
+ */
public SSLContext getContext(List servers) {
for (Map.Entry entry : exacts.entrySet()) {
for (SNIServerName server : servers) {
@@ -60,6 +67,9 @@ public SSLContext getDefaultContext() {
return defaultContext;
}
+ /**
+ * A class which allows building and configuration SNIContextMatcher. The builder, at minimum requres a default SSLContext.
+ */
public static class Builder {
private SSLContext defaultContext;
@@ -91,4 +101,4 @@ public Builder addMatch(String name, SSLContext context) {
return this;
}
}
-}
+}
\ No newline at end of file
diff --git a/ssl/src/main/java/org/wildfly/security/ssl/SNISSLContext.java b/ssl/src/main/java/org/wildfly/security/ssl/SNISSLContext.java
index dbea0b91882..6ab0f43f1c5 100644
--- a/ssl/src/main/java/org/wildfly/security/ssl/SNISSLContext.java
+++ b/ssl/src/main/java/org/wildfly/security/ssl/SNISSLContext.java
@@ -17,9 +17,12 @@
import javax.net.ssl.SSLContext;
+/**
+ * A class that uses the provided SNIContextMatcher to determine the SSLContext to be used for the connection.
+ */
public class SNISSLContext extends SSLContext {
public SNISSLContext(SNIContextMatcher matcher) {
super(new SNISSLContextSpi(matcher), matcher.getDefaultContext().getProvider(), matcher.getDefaultContext().getProtocol());
}
-}
+}
\ No newline at end of file
diff --git a/ssl/src/main/java/org/wildfly/security/ssl/SSLUtils.java b/ssl/src/main/java/org/wildfly/security/ssl/SSLUtils.java
index 4d1285a6c9a..4377fed8663 100644
--- a/ssl/src/main/java/org/wildfly/security/ssl/SSLUtils.java
+++ b/ssl/src/main/java/org/wildfly/security/ssl/SSLUtils.java
@@ -128,10 +128,8 @@ public static SecurityFactory createSslContextFactory(ProtocolSelect
return () -> {
for (String protocol : supportedProtocols) {
List providerList = preferredProviderByAlgorithm.getOrDefault(protocol.toUpperCase(Locale.ENGLISH), Collections.emptyList());
- if (log.isTraceEnabled()) {
- if (providerList.isEmpty()) {
- log.tracef("No providers are available for protocol %s", protocol);
- }
+ if (log.isTraceEnabled() && providerList.isEmpty()) {
+ log.tracef("No providers are available for protocol %s", protocol);
}
for (Provider provider : providerList) {
try {
diff --git a/ssl/src/main/java/org/wildfly/security/ssl/X509CRLExtendedTrustManager.java b/ssl/src/main/java/org/wildfly/security/ssl/X509CRLExtendedTrustManager.java
index 822d19282cd..53042f1305c 100644
--- a/ssl/src/main/java/org/wildfly/security/ssl/X509CRLExtendedTrustManager.java
+++ b/ssl/src/main/java/org/wildfly/security/ssl/X509CRLExtendedTrustManager.java
@@ -52,7 +52,7 @@
* Extension to the {@link X509TrustManager} interface to support CRL verification.
*
* @author Pedro Igor
- * @deprecated use {@link X509RevocationTrustManager} instead
+ * @deprecated use {@link org.wildfly.security.ssl.X509RevocationTrustManager org.wildfly.security.ssl.X509RevocationTrustManager} instead
*/
@Deprecated
public final class X509CRLExtendedTrustManager extends X509ExtendedTrustManager {
diff --git a/ssl/src/main/java/org/wildfly/security/ssl/X509RevocationTrustManager.java b/ssl/src/main/java/org/wildfly/security/ssl/X509RevocationTrustManager.java
index aef9cf3d35e..8e14fa93089 100644
--- a/ssl/src/main/java/org/wildfly/security/ssl/X509RevocationTrustManager.java
+++ b/ssl/src/main/java/org/wildfly/security/ssl/X509RevocationTrustManager.java
@@ -394,11 +394,9 @@ private void checkCertPathLength(X509Certificate currCert) throws CertPathValida
X500Principal issuer = currCert.getIssuerX500Principal();
int pathLenConstraint = -1;
- if (currCert.getVersion() < 3) { // version 1 or version 2
- if (i == 1) {
- if (subject.equals(issuer)) {
- pathLenConstraint = Integer.MAX_VALUE;
- }
+ if (currCert.getVersion() < 3) { // version 1 or version 2
+ if (i == 1 && subject.equals(issuer)) {
+ pathLenConstraint = Integer.MAX_VALUE;
}
} else {
pathLenConstraint = currCert.getBasicConstraints();
@@ -408,15 +406,14 @@ private void checkCertPathLength(X509Certificate currCert) throws CertPathValida
pathLenConstraint = maxPathLength;
}
- if (!subject.equals(issuer)) {
- if (pathLenConstraint < i) {
- throw new CertPathValidatorException
- ("check failed: pathLenConstraint violated - "
- + "this cert must be the last cert in the "
- + "certification path", null, null, -1,
- PKIXReason.PATH_TOO_LONG);
- }
+ if (!subject.equals(issuer) && pathLenConstraint < i) {
+ throw new CertPathValidatorException
+ ("check failed: pathLenConstraint violated - "
+ + "this cert must be the last cert in the "
+ + "certification path", null, null, -1,
+ PKIXReason.PATH_TOO_LONG);
}
+
if (pathLenConstraint < maxPathLength)
maxPathLength = pathLenConstraint;
}
diff --git a/tests/base/pom.xml b/tests/base/pom.xml
index e960945fa6f..10306ce7191 100644
--- a/tests/base/pom.xml
+++ b/tests/base/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
@@ -54,6 +54,7 @@
1.0.2
+ 2.4.0-b180830.0438
@@ -385,7 +386,10 @@
org.wildfly.security
wildfly-elytron-digest
-
+
+ org.wildfly.security
+ wildfly-elytron-http-cert
+
org.wildfly.security
wildfly-elytron-http
@@ -402,6 +406,10 @@
org.wildfly.security
wildfly-elytron-http-digest
+
+ org.wildfly.security
+ wildfly-elytron-http-bearer
+
org.wildfly.security
wildfly-elytron-http-external
@@ -638,11 +646,6 @@
apacheds-kerberos-codec
test
-
- org.apache.directory.server
- apacheds-protocol-kerberos
- test
-
org.apache.directory.server
apacheds-protocol-ldap
@@ -654,6 +657,11 @@
+
+ org.apache.kerby
+ kerb-simplekdc
+ test
+
org.apache.directory.mavibot
mavibot
@@ -709,7 +717,7 @@
- org.glassfish
+ org.eclipse.parsson
jakarta.json
test
@@ -766,27 +774,21 @@
test
- com.sun.xml.bind
- jaxb-core
- 2.3.0
- test
-
-
- com.sun.xml.bind
- jaxb-impl
- 2.3.0
+ org.glassfish.jaxb
+ jaxb-runtime
+ ${version.org.glassfish.jaxb.jaxb-runtime}
test
org.hsqldb
hsqldb
- 2.3.1
+ 2.7.1
test
org.wildfly.security
- wildfly-elytron-credential
+ wildfly-elytron-dynamic-ssl
test
@@ -800,7 +802,7 @@
- --add-modules java.sql --illegal-access=permit
+ --add-modules java.sql --illegal-access=permit --add-exports=jdk.security.jgss/com.sun.security.sasl.gsskerb=ALL-UNNAMED
-Djdk.attach.allowAttachSelf=true
@@ -822,7 +824,7 @@
com.github.siom79.japicmp
japicmp-maven-plugin
- 0.13.0
+ 0.20.0
diff --git a/tests/base/src/test/java/org/wildfly/security/apacheds/LdapService.java b/tests/base/src/test/java/org/wildfly/security/apacheds/LdapService.java
index 66405961cbd..6952fa783d2 100644
--- a/tests/base/src/test/java/org/wildfly/security/apacheds/LdapService.java
+++ b/tests/base/src/test/java/org/wildfly/security/apacheds/LdapService.java
@@ -143,7 +143,6 @@ public Builder addPartition(final String id, final String partitionName, final i
for (String current : indexes) {
partitionFactory.addIndex(partition, current, indexSize);
}
- partition.setCacheService(directoryService.getCacheService());
partition.initialize();
directoryService.addPartition(partition);
diff --git a/tests/base/src/test/java/org/wildfly/security/auth/client/MaskedPasswordSSLAuthenticationTest.java b/tests/base/src/test/java/org/wildfly/security/auth/client/MaskedPasswordSSLAuthenticationTest.java
index 119548f4c4b..0ba8e2713a1 100644
--- a/tests/base/src/test/java/org/wildfly/security/auth/client/MaskedPasswordSSLAuthenticationTest.java
+++ b/tests/base/src/test/java/org/wildfly/security/auth/client/MaskedPasswordSSLAuthenticationTest.java
@@ -25,12 +25,10 @@
import java.io.Closeable;
import java.io.IOException;
-import java.io.InputStream;
import java.net.InetAddress;
import java.net.URI;
import java.security.AccessController;
import java.security.KeyStore;
-import java.security.KeyStoreException;
import java.security.PrivilegedAction;
import java.security.Security;
import java.util.Locale;
@@ -38,17 +36,11 @@
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
-import javax.net.ssl.KeyManager;
-import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.TrustManagerFactory;
-import javax.net.ssl.X509ExtendedKeyManager;
-import javax.net.ssl.X509TrustManager;
import org.junit.AfterClass;
import org.junit.BeforeClass;
@@ -63,6 +55,8 @@
import org.wildfly.security.ssl.SSLUtils;
import org.wildfly.security.ssl.test.util.CAGenerationTool;
import org.wildfly.security.ssl.test.util.CAGenerationTool.Identity;
+import org.wildfly.security.ssl.test.util.DefinedCAIdentity;
+import org.wildfly.security.ssl.test.util.DefinedIdentity;
import org.wildfly.security.x500.principal.X500AttributePrincipalDecoder;
/**
@@ -72,64 +66,13 @@
*/
public class MaskedPasswordSSLAuthenticationTest {
- private static final char[] PASSWORD = "Elytron".toCharArray();
+
private static final String JKS_LOCATION = "./target/test-classes/jks";
private static CAGenerationTool caGenerationTool;
- /**
- * Get the key manager backed by the specified key store.
- *
- * @param keystorePath the path to the keystore with X509 private key
- * @return the initialised key manager.
- */
- private static X509ExtendedKeyManager getKeyManager(final String keystorePath) throws Exception {
- KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
- keyManagerFactory.init(createKeyStore(keystorePath), PASSWORD);
-
- for (KeyManager current : keyManagerFactory.getKeyManagers()) {
- if (current instanceof X509ExtendedKeyManager) {
- return (X509ExtendedKeyManager) current;
- }
- }
-
- throw new IllegalStateException("Unable to obtain X509ExtendedKeyManager.");
- }
-
- private static TrustManagerFactory getTrustManagerFactory() throws Exception {
- return TrustManagerFactory.getInstance("PKIX");
- }
-
- /**
- * Get the trust manager that trusts all certificates signed by the certificate authority.
- *
- * @return the trust manager that trusts all certificates signed by the certificate authority.
- * @throws KeyStoreException
- */
- private static X509TrustManager getCATrustManager() throws Exception {
- TrustManagerFactory trustManagerFactory = getTrustManagerFactory();
- trustManagerFactory.init(createKeyStore("/jks/ca.truststore"));
-
- for (TrustManager current : trustManagerFactory.getTrustManagers()) {
- if (current instanceof X509TrustManager) {
- return (X509TrustManager) current;
- }
- }
-
- throw new IllegalStateException("Unable to obtain X509TrustManager.");
- }
-
- private static KeyStore createKeyStore(final String path) throws Exception {
- KeyStore keyStore = KeyStore.getInstance("jks");
- try (InputStream caTrustStoreFile = MaskedPasswordSSLAuthenticationTest.class.getResourceAsStream(path)) {
- keyStore.load(caTrustStoreFile, PASSWORD);
- }
-
- return keyStore;
- }
-
- private static SecurityDomain getKeyStoreBackedSecurityDomain(String keyStorePath) throws Exception {
- SecurityRealm securityRealm = new KeyStoreBackedSecurityRealm(createKeyStore(keyStorePath));
+ private static SecurityDomain getKeyStoreBackedSecurityDomain(KeyStore keyStore) throws Exception {
+ SecurityRealm securityRealm = new KeyStoreBackedSecurityRealm(keyStore);
return SecurityDomain.builder()
.addRealm("KeystoreRealm", securityRealm)
@@ -156,10 +99,13 @@ public static void afterTest() throws IOException {
@Test
public void testTwoWay() throws Exception {
+ DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA);
+ DefinedIdentity scarab = caGenerationTool.getDefinedIdentity(Identity.SCARAB);
+
SSLContext serverContext = new SSLContextBuilder()
- .setSecurityDomain(getKeyStoreBackedSecurityDomain("/jks/beetles.keystore"))
- .setKeyManager(getKeyManager("/jks/scarab.keystore"))
- .setTrustManager(getCATrustManager())
+ .setSecurityDomain(getKeyStoreBackedSecurityDomain(caGenerationTool.getBeetlesKeyStore()))
+ .setKeyManager(scarab.createKeyManager())
+ .setTrustManager(ca.createTrustManager())
.setNeedClientAuth(true)
.build().create();
@@ -191,11 +137,11 @@ private SecurityIdentity performConnectionTest(SSLContext serverContext, SSLCont
SSLSocket sslSocket = (SSLSocket) clientContext.getSocketFactory().createSocket(InetAddress.getLoopbackAddress(), 1111);
sslSocket.getSession();
+ System.out.println("Client connected");
return sslSocket;
} catch (Exception e) {
+ System.out.println("Client Connection Failed");
throw new RuntimeException(e);
- } finally {
- System.out.println("Client connected");
}
});
diff --git a/tests/base/src/test/java/org/wildfly/security/auth/realm/DistributedSecurityRealmTest.java b/tests/base/src/test/java/org/wildfly/security/auth/realm/DistributedSecurityRealmTest.java
index 1e0001c20bc..1a1abbe686b 100644
--- a/tests/base/src/test/java/org/wildfly/security/auth/realm/DistributedSecurityRealmTest.java
+++ b/tests/base/src/test/java/org/wildfly/security/auth/realm/DistributedSecurityRealmTest.java
@@ -133,6 +133,16 @@ public void testExistingIdentity3() throws Exception {
identity.dispose();
}
+ @Test
+ public void testVerifyEvidence() throws Exception {
+
+ RealmIdentity identity = realm.getRealmIdentity(new NamePrincipal("user1"));
+ Assert.assertTrue(identity.verifyEvidence(new PasswordGuessEvidence(pass1)));
+ Assert.assertFalse(identity.verifyEvidence(new PasswordGuessEvidence(pass2)));
+ Assert.assertFalse(identity.verifyEvidence(new PasswordGuessEvidence(pass3)));
+ identity.dispose();
+ }
+
@Test
public void testEvidence() throws Exception {
RealmIdentity identity = realm.getRealmIdentity(new SimpleEvidence("evidenceUser", true));
diff --git a/tests/base/src/test/java/org/wildfly/security/auth/realm/token/JwtSecurityRealmTest.java b/tests/base/src/test/java/org/wildfly/security/auth/realm/token/JwtSecurityRealmTest.java
index 250e0f4e272..917fb30cc47 100644
--- a/tests/base/src/test/java/org/wildfly/security/auth/realm/token/JwtSecurityRealmTest.java
+++ b/tests/base/src/test/java/org/wildfly/security/auth/realm/token/JwtSecurityRealmTest.java
@@ -337,6 +337,7 @@ public void testMultipleTokenTypes() throws Exception {
@Test
public void testUnsecuredJkuEndpoint() throws Exception {
+ checkIdentityDoesNotExist("1", 50832);
BearerTokenEvidence evidence = new BearerTokenEvidence(createJwt(keyPair1, 60, -1, "1", new URI("https://localhost:50832")));
X509TrustManager tm = getTrustManager();
@@ -435,6 +436,7 @@ public void testJkuMultipleKeys() throws Exception {
@Test
public void testInvalidJku() throws Exception {
+ checkIdentityDoesNotExist("1", 80);
BearerTokenEvidence evidence = new BearerTokenEvidence(createJwt(keyPair1, 60, -1, "1", new URI("https://localhost:80")));
X509TrustManager tm = getTrustManager();
@@ -455,6 +457,7 @@ public void testInvalidJku() throws Exception {
@Test
public void testInvalidKid() throws Exception {
+ checkIdentityDoesNotExist("badkid", 50831);
BearerTokenEvidence evidence = new BearerTokenEvidence(createJwt(keyPair1, 60, -1, "badkid", new URI("https://localhost:50831")));
X509TrustManager tm = getTrustManager();
@@ -833,4 +836,20 @@ public MockResponse dispatch(RecordedRequest recordedRequest) {
};
}
+ private void checkIdentityDoesNotExist(String kid, int port) throws Exception {
+ BearerTokenEvidence evidence = new BearerTokenEvidence(createJwt(keyPair1, 60, -1, kid, new URI("https://localhost:" + port)));
+
+ X509TrustManager tm = getTrustManager();
+ SSLContext sslContext = new SSLContextBuilder().setTrustManager(tm).setClientMode(true).setSessionTimeout(10).build().create();
+
+ TokenSecurityRealm securityRealm = TokenSecurityRealm.builder()
+ .principalClaimName("sub")
+ .validator(JwtValidator.builder()
+ .issuer("elytron-oauth2-realm")
+ .audience("my-app-valid")
+ .useSslContext(sslContext).useSslHostnameVerifier((a,b) -> true).build())
+ .build();
+
+ assertIdentityNotExist(securityRealm, evidence);
+ }
}
diff --git a/tests/base/src/test/java/org/wildfly/security/auth/server/CustomNameRewriterTest.java b/tests/base/src/test/java/org/wildfly/security/auth/server/CustomNameRewriterTest.java
new file mode 100644
index 00000000000..c3a8f59d7ab
--- /dev/null
+++ b/tests/base/src/test/java/org/wildfly/security/auth/server/CustomNameRewriterTest.java
@@ -0,0 +1,92 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2023 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.wildfly.security.auth.server;
+
+import org.junit.Test;
+import org.wildfly.security.auth.permission.LoginPermission;
+import org.wildfly.security.auth.realm.FileSystemSecurityRealm;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class CustomNameRewriterTest {
+
+ private static final String BEFORE_USER_NAME = "Bob";
+ private static final String AFTER_USER_NAME = "Robert";
+
+ @Test
+ public void testCustomNameRewriter() {
+ CustomNameRewriter rewriter = new CustomNameRewriter();
+ String adjustedName = rewriter.rewriteName(BEFORE_USER_NAME);
+ assertEquals(AFTER_USER_NAME, adjustedName);
+ }
+
+ @Test
+ public void testCustomNameRewriterAuthentication() throws Exception {
+ FileSystemSecurityRealm fileSystemSecurityRealm = createSecurityRealm();
+ CustomNameRewriter rewriter = new CustomNameRewriter();
+ SecurityDomain domainWithRewriter = SecurityDomain.builder().setDefaultRealmName("default").addRealm("default", fileSystemSecurityRealm).build()
+ .setPermissionMapper(((permissionMappable, roles) -> LoginPermission.getInstance()))
+ .setPreRealmRewriter(rewriter)
+ .build();
+ ServerAuthenticationContext sac1 = domainWithRewriter.createNewAuthenticationContext();
+ sac1.setAuthenticationName(BEFORE_USER_NAME); // security domain contains the user "Robert"
+ assertTrue(sac1.authorize());
+
+ SecurityDomain domainWithoutRewriter = SecurityDomain.builder().setDefaultRealmName("default").addRealm("default", fileSystemSecurityRealm).build()
+ .setPermissionMapper(((permissionMappable, roles) -> LoginPermission.getInstance()))
+ .build();
+ ServerAuthenticationContext sac2 = domainWithoutRewriter.createNewAuthenticationContext();
+ sac2.setAuthenticationName(BEFORE_USER_NAME); // should fail if rewriter not configured
+ assertFalse(sac2.authorize());
+ }
+
+ @Test
+ public void testCustomNameRewriterNonExistingUser() throws Exception{
+ FileSystemSecurityRealm fileSystemSecurityRealm = createSecurityRealm();
+ CustomNameRewriter rewriter = new CustomNameRewriter();
+ SecurityDomain securityDomain = SecurityDomain.builder().setDefaultRealmName("default").addRealm("default", fileSystemSecurityRealm).build()
+ .setPermissionMapper(((permissionMappable, roles) -> LoginPermission.getInstance()))
+ .setPreRealmRewriter(rewriter)
+ .build();
+ ServerAuthenticationContext sac = securityDomain.createNewAuthenticationContext();
+ sac.setAuthenticationName("John");
+ assertFalse(sac.authorize());
+ }
+
+ private FileSystemSecurityRealm createSecurityRealm() throws Exception {
+ FileSystemSecurityRealm realm = new FileSystemSecurityRealm(ServerUtils.getRootPath(true, getClass()));
+ ServerUtils.addUser(realm, AFTER_USER_NAME);
+ return realm;
+ }
+
+ private final class CustomNameRewriter implements NameRewriter {
+
+ @Override
+ public String rewriteName(String original) {
+ if (original == null) {
+ return null;
+ } else if (original.equals(BEFORE_USER_NAME)) {
+ return AFTER_USER_NAME;
+ } else {
+ return original;
+ }
+ }
+ }
+}
diff --git a/tests/base/src/test/java/org/wildfly/security/credential/source/impl/CommandCredentialSourceTest.java b/tests/base/src/test/java/org/wildfly/security/credential/source/impl/CommandCredentialSourceTest.java
index 7f7454c71f6..c41d01bbccd 100644
--- a/tests/base/src/test/java/org/wildfly/security/credential/source/impl/CommandCredentialSourceTest.java
+++ b/tests/base/src/test/java/org/wildfly/security/credential/source/impl/CommandCredentialSourceTest.java
@@ -18,7 +18,8 @@
package org.wildfly.security.credential.source.impl;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertEquals;
import java.io.File;
import java.io.IOException;
@@ -43,7 +44,7 @@ public void testCommand() throws GeneralSecurityException, IOException {
assertNotNull(password);
final ClearPassword clearPassword = password.castAs(ClearPassword.class, ClearPassword.ALGORITHM_CLEAR);
assertNotNull(clearPassword);
- assertEquals(new String(clearPassword.getPassword()), "secret_key_THREE");
+ assertEquals("secret_key_THREE", new String(clearPassword.getPassword()));
}
private static CommandCredentialSource.Builder getBuilder() {
diff --git a/tests/base/src/test/java/org/wildfly/security/credential/store/KeystorePasswordStoreTest.java b/tests/base/src/test/java/org/wildfly/security/credential/store/KeystorePasswordStoreTest.java
index e040f146f1f..b0505bd12ab 100644
--- a/tests/base/src/test/java/org/wildfly/security/credential/store/KeystorePasswordStoreTest.java
+++ b/tests/base/src/test/java/org/wildfly/security/credential/store/KeystorePasswordStoreTest.java
@@ -34,7 +34,7 @@
import java.util.concurrent.locks.ReadWriteLock;
import java.util.function.Supplier;
-import org.apache.commons.lang.RandomStringUtils;
+import org.apache.commons.lang3.RandomStringUtils;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
diff --git a/tests/base/src/test/java/org/wildfly/security/http/HttpAuthenticatorTest.java b/tests/base/src/test/java/org/wildfly/security/http/HttpAuthenticatorTest.java
index e20540e3765..f1bc2e557c6 100644
--- a/tests/base/src/test/java/org/wildfly/security/http/HttpAuthenticatorTest.java
+++ b/tests/base/src/test/java/org/wildfly/security/http/HttpAuthenticatorTest.java
@@ -64,7 +64,7 @@ public class HttpAuthenticatorTest extends AbstractBaseHttpTest {
" qop=auth,\n" +
" response=\"8ca523f5e9506fed4657c9700eebdbec\",\n" +
" opaque=\"FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS\"";
- private final String digestSha256Header = "Digest username=\"Mufasa\",\n"
+ private final String digestSha256Header = "Digest username=\"Mufasa\",\n"
+ " realm=\"http-auth@example.org\",\n"
+ " uri=\"/dir/index.html\",\n"
+ " algorithm=SHA-256,\n"
@@ -180,7 +180,7 @@ public List prepareBasicSilentMechanisms() th
return mechanisms;
}
- public void prepareSilentBasicWithDigestMechanisms() throws Exception{
+ public void prepareSilentBasicWithDigestMechanisms() throws Exception {
List mechanisms = prepareBasicSilentMechanisms();
Map digestProps = new HashMap<>();
digestProps.put(CONFIG_REALM, "http-auth@example.org");
@@ -219,7 +219,7 @@ public void testBasicSilent() throws Exception {
}
@Test
- public void testBasicSilentWithDigest() throws Exception{
+ public void testBasicSilentWithDigest() throws Exception {
// authenticate using only DIGEST mechanism
prepareSilentBasicWithDigestMechanisms();
authenticateWithDigestMD5();
@@ -255,5 +255,4 @@ public void testUsingSecurityProviderServerMechanismWithDigestMD5() throws Excep
authenticateWithDigestMD5();
}
-
}
diff --git a/tests/base/src/test/java/org/wildfly/security/http/bearer/BearerAuthenticationMechanismTest.java b/tests/base/src/test/java/org/wildfly/security/http/bearer/BearerAuthenticationMechanismTest.java
new file mode 100644
index 00000000000..a3087b37ad4
--- /dev/null
+++ b/tests/base/src/test/java/org/wildfly/security/http/bearer/BearerAuthenticationMechanismTest.java
@@ -0,0 +1,58 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2023 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.wildfly.security.http.bearer;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.wildfly.security.http.HttpServerAuthenticationMechanism;
+import org.wildfly.security.http.impl.AbstractBaseHttpTest;
+
+import static org.wildfly.security.http.HttpConstants.BEARER_TOKEN;
+import static org.wildfly.security.http.HttpConstants.UNAUTHORIZED;
+
+import java.util.Collections;
+
+/**
+ * Test of server side of the Bearer HTTP mechanism.
+ *
+ * @author Keshav Kumar
+ */
+public class BearerAuthenticationMechanismTest extends AbstractBaseHttpTest {
+
+ @Test
+ public void testBearerAuthenticationMechanism() throws Exception {
+ HttpServerAuthenticationMechanism mechanism = bearerFactory.createAuthenticationMechanism(BEARER_TOKEN, Collections.emptyMap(), getCallbackHandler(null, "testrealm@host.com", null, "random"));
+
+ //Test no authentication in progress
+ TestingHttpServerRequest request1 = new TestingHttpServerRequest(new String[]{});
+ mechanism.evaluateRequest(request1);
+ Assert.assertEquals(Status.NO_AUTH, request1.getResult());
+
+ //Test unsuccessful authentication
+ TestingHttpServerRequest request2 = new TestingHttpServerRequest(new String[]{"Bearer test"});
+ mechanism.evaluateRequest(request2);
+ Assert.assertEquals(Status.FAILED, request2.getResult());
+ Assert.assertEquals(UNAUTHORIZED, request2.getResponse().getStatusCode());
+
+ //Test successful Authentication
+ TestingHttpServerRequest request3 = new TestingHttpServerRequest(new String[]{"Bearer random"});
+ mechanism.evaluateRequest(request3);
+ Assert.assertEquals(Status.COMPLETE, request3.getResult());
+ }
+}
diff --git a/tests/base/src/test/java/org/wildfly/security/http/cert/ClientCertAuthenticationMechanismTest.java b/tests/base/src/test/java/org/wildfly/security/http/cert/ClientCertAuthenticationMechanismTest.java
new file mode 100644
index 00000000000..6180ab1ed44
--- /dev/null
+++ b/tests/base/src/test/java/org/wildfly/security/http/cert/ClientCertAuthenticationMechanismTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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.cert;
+
+import mockit.Tested;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.wildfly.security.auth.realm.SimpleMapBackedSecurityRealm;
+import org.wildfly.security.auth.server.SecurityDomain;
+import org.wildfly.security.cache.IdentityCache;
+import org.wildfly.security.http.HttpAuthenticationException;
+import org.wildfly.security.http.HttpServerAuthenticationMechanism;
+import org.wildfly.security.http.impl.AbstractBaseHttpTest;
+
+import javax.security.auth.x500.X500Principal;
+import java.security.Provider;
+import java.security.Security;
+import java.util.HashMap;
+import java.util.Map;
+import static org.wildfly.security.http.HttpConstants.*;
+
+public class ClientCertAuthenticationMechanismTest extends AbstractBaseHttpTest {
+ private static final Provider provider = WildFlyElytronHttpClientCertProvider.getInstance();
+
+ @Tested
+ private IdentityCache identityCache;
+
+ @BeforeClass
+ public static void registerCertProvider() {
+ Security.insertProviderAt(provider, 1);
+ SecurityDomain securityDomain = SecurityDomain.builder().addRealm("Simple", new SimpleMapBackedSecurityRealm()).build().setDefaultRealmName("Simple").build();
+ }
+
+ @AfterClass
+ public static void removeCertProvider() {
+ Security.removeProvider(provider.getName());
+ }
+
+ private HttpServerAuthenticationMechanism createMechanism() throws HttpAuthenticationException {
+ Map props = new HashMap<>();
+ return certFactory.createAuthenticationMechanism(CLIENT_CERT_NAME, props, getCallbackHandler("Duk3"));
+ }
+
+ //Test request with no certs
+ @Test
+ public void testNoCert() throws Exception {
+ TestingHttpServerRequest request = new TestingHttpServerRequest(new String[]{});
+ createMechanism().evaluateRequest(request);
+ Assert.assertEquals(Status.NO_AUTH, request.getResult());
+ }
+
+ //Test request with invalid/unknown cert
+ @Test
+ public void testUnknownCert() throws Exception {
+ TestingHttpServerRequest request = new TestingHttpServerRequest(new String[]{"Cert random"}, new X500Principal("CN=Duke, OU=Test, O=Wonderland, C=US"));
+ createMechanism().evaluateRequest(request);
+ Assert.assertEquals(Status.FAILED, request.getResult());
+ }
+
+ //Test request with known cert
+ @Test
+ public void testKnownCert() throws Exception {
+ TestingHttpServerRequest request = new TestingHttpServerRequest(new String[]{"Cert test"}, new X500Principal("CN=Duk3, OU=T3st, O=W0nd3rl4nd, C=US"));
+ createMechanism().evaluateRequest(request);
+ Assert.assertEquals(Status.COMPLETE, request.getResult());
+ }
+}
diff --git a/tests/base/src/test/java/org/wildfly/security/http/digest/DigestAuthenticationMechanismTest.java b/tests/base/src/test/java/org/wildfly/security/http/digest/DigestAuthenticationMechanismTest.java
index 1da396d9a58..a38274fe6a5 100644
--- a/tests/base/src/test/java/org/wildfly/security/http/digest/DigestAuthenticationMechanismTest.java
+++ b/tests/base/src/test/java/org/wildfly/security/http/digest/DigestAuthenticationMechanismTest.java
@@ -63,6 +63,18 @@ public static void removePasswordProvider() {
Security.removeProvider(provider.getName());
}
+ public void evaluateRequest(String[] authorization, HttpServerAuthenticationMechanism mechanism) throws Exception{
+ TestingHttpServerRequest request = new TestingHttpServerRequest(authorization);
+ mechanism.evaluateRequest(request);
+ Assert.assertEquals(Status.COMPLETE, request.getResult());
+ }
+
+ public void evaluateRequest(String[] authorization, HttpServerAuthenticationMechanism mechanism, String uri) throws Exception{
+ TestingHttpServerRequest request = new TestingHttpServerRequest(authorization, new URI(uri));
+ mechanism.evaluateRequest(request);
+ Assert.assertEquals(Status.COMPLETE, request.getResult());
+ }
+
@Test
public void testRfc2617() throws Exception {
mockDigestNonce("AAAAAQABsxiWa25/kpFxsPCrpDCFsjkTzs/Xr7RPsi/VVN6faYp21Hia3h4=");
@@ -78,7 +90,7 @@ public void testRfc2617() throws Exception {
Assert.assertEquals(UNAUTHORIZED, response.getStatusCode());
Assert.assertEquals("Digest realm=\"testrealm@host.com\", nonce=\"AAAAAQABsxiWa25/kpFxsPCrpDCFsjkTzs/Xr7RPsi/VVN6faYp21Hia3h4=\", opaque=\"00000000000000000000000000000000\", algorithm=MD5, qop=auth", response.getAuthenticateHeader());
- TestingHttpServerRequest request2 = new TestingHttpServerRequest(new String[] {
+ evaluateRequest(new String[] {
"Digest username=\"Mufasa\",\n" +
" realm=\"testrealm@host.com\",\n" +
" nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\",\n" +
@@ -89,9 +101,21 @@ public void testRfc2617() throws Exception {
" response=\"" + computeDigest("/dir/index.html", "dcd98b7102dd2f0e8b11d0f600bfb0c093", "0a4f113b", "00000001", "Mufasa", "Circle Of Life", "MD5", "testrealm@host.com", "auth", "GET") + "\",\n" +
" opaque=\"00000000000000000000000000000000\",\n" +
" algorithm=MD5"
- });
- mechanism.evaluateRequest(request2);
- Assert.assertEquals(Status.COMPLETE, request2.getResult());
+ },mechanism);
+
+ // test case insensitive
+ evaluateRequest(new String[] {
+ "DiGeSt username=\"Mufasa\",\n" +
+ " realm=\"testrealm@host.com\",\n" +
+ " nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\",\n" +
+ " uri=\"/dir/index.html\",\n" +
+ " qop=auth,\n" +
+ " nc=00000001,\n" +
+ " cnonce=\"0a4f113b\",\n" +
+ " response=\"" + computeDigest("/dir/index.html", "dcd98b7102dd2f0e8b11d0f600bfb0c093", "0a4f113b", "00000001", "Mufasa", "Circle Of Life", "MD5", "testrealm@host.com", "auth", "GET") + "\",\n" +
+ " opaque=\"00000000000000000000000000000000\",\n" +
+ " algorithm=MD5"
+ },mechanism);
}
@Test
@@ -104,7 +128,8 @@ public void testRfc2617EncodedQuery() throws Exception {
String path = "/dir/index.html?foo=b%2Fr";
String uri = "http://localhost" + path;
- TestingHttpServerRequest request2 = new TestingHttpServerRequest(new String[] {
+
+ evaluateRequest(new String[]{
"Digest username=\"Mufasa\",\n" +
" realm=\"testrealm@host.com\",\n" +
" nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\",\n" +
@@ -115,9 +140,7 @@ public void testRfc2617EncodedQuery() throws Exception {
" response=\"" + computeDigest("http://localhost/dir/index.html?foo=b%2Fr", "dcd98b7102dd2f0e8b11d0f600bfb0c093", "0a4f113b", "00000001", "Mufasa", "Circle Of Life", "MD5", "testrealm@host.com", "auth", "GET") + "\",\n" +
" opaque=\"00000000000000000000000000000000\",\n" +
" algorithm=MD5"
- }, new URI(uri));
- mechanism.evaluateRequest(request2);
- Assert.assertEquals(Status.COMPLETE, request2.getResult());
+ },mechanism,uri);
}
@Test
@@ -130,7 +153,8 @@ public void testRfc2617EncodedPath() throws Exception {
String path = "/dir/foo%2Fr/index.html?foo=b%2Fr";
String uri = "http://localhost" + path;
- TestingHttpServerRequest request2 = new TestingHttpServerRequest(new String[] {
+
+ evaluateRequest(new String[] {
"Digest username=\"Mufasa\",\n" +
" realm=\"testrealm@host.com\",\n" +
" nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\",\n" +
@@ -141,9 +165,7 @@ public void testRfc2617EncodedPath() throws Exception {
" response=\"" + computeDigest("http://localhost/dir/foo%2Fr/index.html?foo=b%2Fr", "dcd98b7102dd2f0e8b11d0f600bfb0c093", "0a4f113b", "00000001", "Mufasa", "Circle Of Life", "MD5", "testrealm@host.com", "auth", "GET") + "\",\n" +
" opaque=\"00000000000000000000000000000000\",\n" +
" algorithm=MD5"
- }, new URI(uri));
- mechanism.evaluateRequest(request2);
- Assert.assertEquals(Status.COMPLETE, request2.getResult());
+ },mechanism, uri);
}
@Test
@@ -161,7 +183,7 @@ public void testRfc7616sha256() throws Exception {
Assert.assertEquals(UNAUTHORIZED, response.getStatusCode());
Assert.assertEquals("Digest realm=\"http-auth@example.org\", nonce=\"7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v\", opaque=\"00000000000000000000000000000000\", algorithm=SHA-256, qop=auth", response.getAuthenticateHeader());
- TestingHttpServerRequest request2 = new TestingHttpServerRequest(new String[] {
+ evaluateRequest(new String[] {
"Digest username=\"Mufasa\",\n" +
" realm=\"http-auth@example.org\",\n" +
" uri=\"/dir/index.html\",\n" +
@@ -172,9 +194,7 @@ public void testRfc7616sha256() throws Exception {
" qop=auth,\n" +
" response=\"" + computeDigest("/dir/index.html", "7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v", "f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ", "00000001", "Mufasa", "Circle of Life", "SHA-256", "http-auth@example.org", "auth", "GET") + "\",\n" +
" opaque=\"FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS\""
- });
- mechanism.evaluateRequest(request2);
- Assert.assertEquals(Status.COMPLETE, request2.getResult());
+ },mechanism);
}
@Test
@@ -192,7 +212,7 @@ public void testSha512_256() throws Exception {
Assert.assertEquals(UNAUTHORIZED, response.getStatusCode());
Assert.assertEquals("Digest realm=\"api@example.org\", nonce=\"5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK\", opaque=\"00000000000000000000000000000000\", algorithm=SHA-512-256, qop=auth", response.getAuthenticateHeader());
- TestingHttpServerRequest request2 = new TestingHttpServerRequest(new String[] {
+ evaluateRequest(new String[] {
"Digest username*=UTF-8''J%C3%A4s%C3%B8n%20Doe,\n" +
" realm=\"api@example.org\",\n" +
" uri=\"/doe.json\",\n" +
@@ -204,6 +224,68 @@ public void testSha512_256() throws Exception {
" response=\"" + computeDigest("/doe.json", "5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK", "NTg6RKcb9boFIAS3KrFK9BGeh+iDa/sm6jUMp2wds69v", "00000001", "J\u00E4s\u00F8n Doe", "Secret, or not?", "SHA-512-256", "api@example.org", "auth", "GET") + "\",\n" +
" opaque=\"00000000000000000000000000000000\",\n" +
" userhash=false"
+ },mechanism);
+ }
+
+ @Test
+ public void testSha256WithDigestPassword() throws Exception {
+ mockDigestNonce("5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK");
+ Map props = new HashMap<>();
+ props.put(CONFIG_REALM, "api@example.org");
+ props.put("org.wildfly.security.http.validate-digest-uri", "false");
+ HttpServerAuthenticationMechanism mechanism = digestFactory.createAuthenticationMechanism(DIGEST_NAME + "-" + SHA256, props, getCallbackHandler("J\u00E4s\u00F8n Doe", "api@example.org", "Secret, or not?", true));
+
+ TestingHttpServerRequest request1 = new TestingHttpServerRequest(null);
+ mechanism.evaluateRequest(request1);
+ Assert.assertEquals(Status.NO_AUTH, request1.getResult());
+ TestingHttpServerResponse response = request1.getResponse();
+ Assert.assertEquals(UNAUTHORIZED, response.getStatusCode());
+ Assert.assertEquals("Digest realm=\"api@example.org\", nonce=\"5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK\", opaque=\"00000000000000000000000000000000\", algorithm=SHA-256, qop=auth", response.getAuthenticateHeader());
+
+ TestingHttpServerRequest request2 = new TestingHttpServerRequest(new String[] {
+ "Digest username*=UTF-8''J%C3%A4s%C3%B8n%20Doe,\n" +
+ " realm=\"api@example.org\",\n" +
+ " uri=\"/doe.json\",\n" +
+ " algorithm=SHA-256,\n" +
+ " nonce=\"5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK\",\n" +
+ " nc=00000001,\n" +
+ " cnonce=\"NTg6RKcb9boFIAS3KrFK9BGeh+iDa/sm6jUMp2wds69v\",\n" +
+ " qop=auth,\n" +
+ " response=\"" + computeDigest("/doe.json", "5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK", "NTg6RKcb9boFIAS3KrFK9BGeh+iDa/sm6jUMp2wds69v", "00000001", "J\u00E4s\u00F8n Doe", "Secret, or not?", "SHA-256", "api@example.org", "auth", "GET") + "\",\n" +
+ " opaque=\"00000000000000000000000000000000\",\n" +
+ " userhash=false"
+ });
+ mechanism.evaluateRequest(request2);
+ Assert.assertEquals(Status.COMPLETE, request2.getResult());
+ }
+
+ @Test
+ public void testDigestMD5Password() throws Exception {
+ mockDigestNonce("5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK");
+ Map props = new HashMap<>();
+ props.put(CONFIG_REALM, "api@example.org");
+ props.put("org.wildfly.security.http.validate-digest-uri", "false");
+ HttpServerAuthenticationMechanism mechanism = digestFactory.createAuthenticationMechanism(DIGEST_NAME, props, getCallbackHandler("J\u00E4s\u00F8n Doe", "api@example.org", "Secret, or not?", true));
+
+ TestingHttpServerRequest request1 = new TestingHttpServerRequest(null);
+ mechanism.evaluateRequest(request1);
+ Assert.assertEquals(Status.NO_AUTH, request1.getResult());
+ TestingHttpServerResponse response = request1.getResponse();
+ Assert.assertEquals(UNAUTHORIZED, response.getStatusCode());
+ Assert.assertEquals("Digest realm=\"api@example.org\", nonce=\"5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK\", opaque=\"00000000000000000000000000000000\", algorithm=MD5, qop=auth", response.getAuthenticateHeader());
+
+ TestingHttpServerRequest request2 = new TestingHttpServerRequest(new String[] {
+ "Digest username*=UTF-8''J%C3%A4s%C3%B8n%20Doe,\n" +
+ " realm=\"api@example.org\",\n" +
+ " uri=\"/doe.json\",\n" +
+ " algorithm=MD5,\n" +
+ " nonce=\"5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK\",\n" +
+ " nc=00000001,\n" +
+ " cnonce=\"NTg6RKcb9boFIAS3KrFK9BGeh+iDa/sm6jUMp2wds69v\",\n" +
+ " qop=auth,\n" +
+ " response=\"" + computeDigest("/doe.json", "5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK", "NTg6RKcb9boFIAS3KrFK9BGeh+iDa/sm6jUMp2wds69v", "00000001", "J\u00E4s\u00F8n Doe", "Secret, or not?", "MD5", "api@example.org", "auth", "GET") + "\",\n" +
+ " opaque=\"00000000000000000000000000000000\",\n" +
+ " userhash=false"
});
mechanism.evaluateRequest(request2);
Assert.assertEquals(Status.COMPLETE, request2.getResult());
@@ -238,4 +320,4 @@ private String encode(String src, MessageDigest md) {
}
return res.toString();
}
-}
+}
\ No newline at end of file
diff --git a/tests/base/src/test/java/org/wildfly/security/http/impl/AbstractBaseHttpTest.java b/tests/base/src/test/java/org/wildfly/security/http/impl/AbstractBaseHttpTest.java
index 5ffc16fa88b..fd4513e18b1 100644
--- a/tests/base/src/test/java/org/wildfly/security/http/impl/AbstractBaseHttpTest.java
+++ b/tests/base/src/test/java/org/wildfly/security/http/impl/AbstractBaseHttpTest.java
@@ -31,6 +31,7 @@
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -40,31 +41,34 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
-
import javax.net.ssl.SSLSession;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.x500.X500Principal;
import javax.security.sasl.AuthorizeCallback;
import javax.security.sasl.RealmCallback;
-
import org.hamcrest.CoreMatchers;
import org.hamcrest.MatcherAssert;
import org.junit.Assert;
-
import org.wildfly.security.auth.callback.AuthenticationCompleteCallback;
import org.wildfly.security.auth.callback.AvailableRealmsCallback;
import org.wildfly.security.auth.callback.CachedIdentityAuthorizeCallback;
import org.wildfly.security.auth.callback.CredentialCallback;
import org.wildfly.security.auth.callback.EvidenceVerifyCallback;
import org.wildfly.security.auth.callback.IdentityCredentialCallback;
+import org.wildfly.security.auth.callback.PrincipalAuthorizeCallback;
import org.wildfly.security.auth.server.SecurityIdentity;
import org.wildfly.security.authz.Roles;
+import org.wildfly.security.credential.BearerTokenCredential;
import org.wildfly.security.credential.Credential;
import org.wildfly.security.credential.PasswordCredential;
+import org.wildfly.security.evidence.BearerTokenEvidence;
import org.wildfly.security.evidence.PasswordGuessEvidence;
+import org.wildfly.security.evidence.X509PeerCertificateChainEvidence;
import org.wildfly.security.http.HttpAuthenticationException;
import org.wildfly.security.http.HttpExchangeSpi;
import org.wildfly.security.http.HttpScope;
@@ -75,31 +79,40 @@
import org.wildfly.security.http.HttpServerResponse;
import org.wildfly.security.http.Scope;
import org.wildfly.security.http.basic.BasicMechanismFactory;
+import org.wildfly.security.http.bearer.BearerMechanismFactory;
+import org.wildfly.security.http.cert.ClientCertMechanismFactory;
import org.wildfly.security.http.digest.DigestMechanismFactory;
import org.wildfly.security.http.digest.NonceManager;
import org.wildfly.security.http.external.ExternalMechanismFactory;
import org.wildfly.security.password.Password;
import org.wildfly.security.password.PasswordFactory;
import org.wildfly.security.password.interfaces.ClearPassword;
+import org.wildfly.security.password.interfaces.DigestPassword;
import org.wildfly.security.password.spec.ClearPasswordSpec;
+import org.wildfly.security.password.spec.DigestPasswordAlgorithmSpec;
+import org.wildfly.security.password.spec.EncryptablePasswordSpec;
+import org.wildfly.security.x500.cert.SelfSignedX509CertificateAndSigningKey;
import mockit.Mock;
import mockit.MockUp;
-// has dependency on wildfly-elytron-sasl, wildfly-elytron-http-basic and wildfly-elytron-digest
+// has dependency on wildfly-elytron-sasl, wildfly-elytron-http-cert, wildfly-elytron-http-basic and wildfly-elytron-digest
public class AbstractBaseHttpTest {
protected HttpServerAuthenticationMechanismFactory basicFactory = new BasicMechanismFactory(ELYTRON_PASSWORD_PROVIDERS.get());
protected HttpServerAuthenticationMechanismFactory digestFactory = new DigestMechanismFactory(ELYTRON_PASSWORD_PROVIDERS.get());
protected final HttpServerAuthenticationMechanismFactory externalFactory = new ExternalMechanismFactory(ELYTRON_PASSWORD_PROVIDERS.get());
+ protected final HttpServerAuthenticationMechanismFactory bearerFactory = new BearerMechanismFactory(ELYTRON_PASSWORD_PROVIDERS.get());
+ protected HttpServerAuthenticationMechanismFactory certFactory = new ClientCertMechanismFactory(ELYTRON_PASSWORD_PROVIDERS.get()[0]);
protected HttpServerAuthenticationMechanismFactory statefulBasicFactory = new org.wildfly.security.http.sfbasic.BasicMechanismFactory(ELYTRON_PASSWORD_PROVIDERS.get());
- protected void mockDigestNonce(final String nonce){
- new MockUp(){
+ protected void mockDigestNonce(final String nonce) {
+ new MockUp() {
@Mock
String generateNonce(byte[] salt) {
return nonce;
}
+
@Mock
boolean useNonce(final String nonce, byte[] salt, int nonceCount) {
return true;
@@ -113,6 +126,7 @@ protected SecurityIdentity mockSecurityIdentity(Principal p) {
public Principal getPrincipal() {
return p;
}
+
@Mock
public Roles getRoles() {
return Roles.NONE;
@@ -137,6 +151,7 @@ protected static class TestingHttpServerRequest implements HttpServerRequest {
private List cookies;
private String requestMethod = "GET";
private Map> requestHeaders = new HashMap<>();
+ private X500Principal testPrincipal = null;
private Map sessionScopeAttachments = new HashMap<>();
public TestingHttpServerRequest(String[] authorization) {
@@ -147,6 +162,15 @@ public TestingHttpServerRequest(String[] authorization) {
this.cookies = new ArrayList<>();
}
+ public TestingHttpServerRequest(String[] authorization, X500Principal principal) {
+ if (authorization != null) {
+ requestHeaders.put(AUTHORIZATION, Arrays.asList(authorization));
+ }
+ this.remoteUser = null;
+ this.cookies = new ArrayList<>();
+ this.testPrincipal = principal;
+ }
+
public TestingHttpServerRequest(String[] authorization, URI requestURI) {
if (authorization != null) {
requestHeaders.put(AUTHORIZATION, Arrays.asList(authorization));
@@ -193,51 +217,10 @@ public TestingHttpServerRequest(String[] authorization, URI requestURI, String c
if (cookie != null) {
final String cookieName = cookie.substring(0, cookie.indexOf('='));
final String cookieValue = cookie.substring(cookie.indexOf('=') + 1);
- cookies.add(new HttpServerCookie() {
- @Override
- public String getName() {
- return cookieName;
- }
-
- @Override
- public String getValue() {
- return cookieValue;
- }
-
- @Override
- public String getDomain() {
- return null;
- }
-
- @Override
- public int getMaxAge() {
- return -1;
- }
-
- @Override
- public String getPath() {
- return "/";
- }
-
- @Override
- public boolean isSecure() {
- return false;
- }
-
- @Override
- public int getVersion() {
- return 0;
- }
-
- @Override
- public boolean isHttpOnly() {
- return true;
- }
- });
+ cookies.add(HttpServerCookie.getInstance(cookieName, cookieValue, null, -1, "/", false, 0, true));
}
}
-
public Status getResult() {
return result;
}
@@ -257,12 +240,18 @@ public String getFirstRequestHeaderValue(String headerName) {
return headerValues != null ? headerValues.get(0) : null;
}
+
public SSLSession getSSLSession() {
- throw new IllegalStateException();
+ return null;
}
public Certificate[] getPeerCertificates() {
- throw new IllegalStateException();
+ if (testPrincipal != null) {
+ X509Certificate cert1 = SelfSignedX509CertificateAndSigningKey.builder().setDn(testPrincipal).build().getSelfSignedCertificate();
+ return new Certificate[]{ cert1 };
+ }
+
+ return null;
}
public void noAuthenticationInProgress(HttpServerMechanismsResponder responder) {
@@ -343,45 +332,48 @@ public boolean resumeRequest() {
}
public HttpScope getScope(Scope scope) {
- return new HttpScope() {
-
- @Override
- public boolean exists() {
- return true;
- }
+ if (scope.equals(Scope.SSL_SESSION)) {
+ return null;
+ } else {
+ return new HttpScope() {
- @Override
- public boolean create() {
- return false;
- }
+ @Override
+ public boolean exists() {
+ return true;
+ }
- @Override
- public boolean supportsAttachments() {
- return true;
- }
+ @Override
+ public boolean create() {
+ return false;
+ }
- @Override
- public boolean supportsInvalidation() {
- return false;
- }
+ @Override
+ public boolean supportsAttachments() {
+ return true;
+ }
- @Override
- public void setAttachment(String key, Object value) {
- if (scope.equals(Scope.SESSION)) {
- sessionScopeAttachments.put(key, value);
+ @Override
+ public boolean supportsInvalidation() {
+ return false;
}
- }
- @Override
- public Object getAttachment(String key) {
- if (scope.equals(Scope.SESSION)) {
- return sessionScopeAttachments.get(key);
- } else {
- return null;
+ @Override
+ public void setAttachment(String key, Object value) {
+ if (scope.equals(Scope.SESSION)) {
+ sessionScopeAttachments.put(key, value);
+ }
}
- }
- };
+ @Override
+ public Object getAttachment(String key) {
+ if (scope.equals(Scope.SESSION)) {
+ return sessionScopeAttachments.get(key);
+ } else {
+ return null;
+ }
+ }
+ };
+ }
}
public Collection getScopeIds(Scope scope) {
@@ -392,7 +384,7 @@ public HttpScope getScope(Scope scope, String id) {
throw new IllegalStateException();
}
- public void setRemoteUser (String remoteUser) {
+ public void setRemoteUser(String remoteUser) {
this.remoteUser = remoteUser;
}
@@ -459,9 +451,25 @@ public boolean forward(String path) {
}
}
+ protected CallbackHandler getCallbackHandler(String realm) {
+ return getCallbackHandler(null, realm, null);
+ };
+
protected CallbackHandler getCallbackHandler(String username, String realm, String password) {
+ return getCallbackHandler(username, realm, password, null, false);
+ }
+
+ protected CallbackHandler getCallbackHandler(String username, String realm, String password, String token) {
+ return getCallbackHandler(username, realm, password, token, false);
+ }
+
+ protected CallbackHandler getCallbackHandler(String username, String realm, String password, boolean useDigestPassword) {
+ return getCallbackHandler(username, realm, password, null, useDigestPassword);
+ }
+
+ protected CallbackHandler getCallbackHandler(String username, String realm, String password, String token, boolean useDigestPassword) {
return callbacks -> {
- for(Callback callback : callbacks) {
+ for (Callback callback : callbacks) {
if (callback instanceof AvailableRealmsCallback) {
((AvailableRealmsCallback) callback).setRealmNames(realm);
} else if (callback instanceof RealmCallback) {
@@ -469,38 +477,75 @@ protected CallbackHandler getCallbackHandler(String username, String realm, Stri
} else if (callback instanceof NameCallback) {
Assert.assertEquals(username, ((NameCallback) callback).getDefaultName());
} else if (callback instanceof CredentialCallback) {
- if (!ClearPassword.ALGORITHM_CLEAR.equals(((CredentialCallback) callback).getAlgorithm())) {
- throw new UnsupportedCallbackException(callback);
- }
- try {
- PasswordFactory factory = PasswordFactory.getInstance(ClearPassword.ALGORITHM_CLEAR, ELYTRON_PASSWORD_PROVIDERS);
- Password pass = factory.generatePassword(new ClearPasswordSpec(password.toCharArray()));
- ((CredentialCallback) callback).setCredential(new PasswordCredential(pass));
- } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
- throw new IllegalStateException(e);
+ if (useDigestPassword) {
+ String credentialAlgorithm = ((CredentialCallback) callback).getAlgorithm();
+ if (! DigestPassword.ALGORITHM_DIGEST_SHA_256.equals(credentialAlgorithm) &&
+ ! DigestPassword.ALGORITHM_DIGEST_MD5.equals(credentialAlgorithm)) {
+ throw new UnsupportedCallbackException(callback);
+ }
+ try {
+ PasswordFactory factory = PasswordFactory.getInstance(credentialAlgorithm, ELYTRON_PASSWORD_PROVIDERS);
+ DigestPasswordAlgorithmSpec algorithmSpec = new DigestPasswordAlgorithmSpec(username, realm);
+ EncryptablePasswordSpec encryptableSpec = new EncryptablePasswordSpec(password.toCharArray(), algorithmSpec);
+ DigestPassword digestPassword = (DigestPassword) factory.generatePassword(encryptableSpec);
+ ((CredentialCallback) callback).setCredential(new PasswordCredential(digestPassword));
+ } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+ throw new IllegalStateException(e);
+ }
+ } else {
+ if (!ClearPassword.ALGORITHM_CLEAR.equals(((CredentialCallback) callback).getAlgorithm())) {
+ throw new UnsupportedCallbackException(callback);
+ }
+ try {
+ PasswordFactory factory = PasswordFactory.getInstance(ClearPassword.ALGORITHM_CLEAR, ELYTRON_PASSWORD_PROVIDERS);
+ Password pass = factory.generatePassword(new ClearPasswordSpec(password.toCharArray()));
+ ((CredentialCallback) callback).setCredential(new PasswordCredential(pass));
+ } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+ throw new IllegalStateException(e);
+ }
}
} else if (callback instanceof EvidenceVerifyCallback) {
- PasswordGuessEvidence evidence = (PasswordGuessEvidence) ((EvidenceVerifyCallback) callback).getEvidence();
- ((EvidenceVerifyCallback) callback).setVerified(Arrays.equals(evidence.getGuess(), password.toCharArray()));
- evidence.destroy();
+ if (((EvidenceVerifyCallback) callback).getEvidence() instanceof PasswordGuessEvidence) {
+ PasswordGuessEvidence evidence = (PasswordGuessEvidence) ((EvidenceVerifyCallback) callback).getEvidence();
+ ((EvidenceVerifyCallback) callback).setVerified(Arrays.equals(evidence.getGuess(), password.toCharArray()));
+ evidence.destroy();
+ } else if (((EvidenceVerifyCallback) callback).getEvidence() instanceof BearerTokenEvidence) {
+ BearerTokenEvidence evidence = (BearerTokenEvidence) ((EvidenceVerifyCallback) callback).getEvidence();
+ ((EvidenceVerifyCallback) callback).setVerified(Objects.equals(token, evidence.getToken()));
+ } else if (((EvidenceVerifyCallback) callback).getEvidence() instanceof X509PeerCertificateChainEvidence) {
+ X509PeerCertificateChainEvidence evidence = (X509PeerCertificateChainEvidence) ((EvidenceVerifyCallback) callback).getEvidence();
+ evidence.setDecodedPrincipal(evidence.getFirstCertificate().getIssuerX500Principal());
+ ((EvidenceVerifyCallback) callback).setVerified("CN=Duk3,OU=T3st,O=W0nd3rl4nd,C=US".equals(evidence.getFirstCertificate().getIssuerX500Principal().getName()));
+ }
} else if (callback instanceof AuthenticationCompleteCallback) {
// NO-OP
} else if (callback instanceof IdentityCredentialCallback) {
Credential credential = ((IdentityCredentialCallback) callback).getCredential();
- MatcherAssert.assertThat(credential, CoreMatchers.instanceOf(PasswordCredential.class));
- ClearPassword clearPwdCredential = ((PasswordCredential) credential).getPassword().castAs(ClearPassword.class);
- Assert.assertNotNull(clearPwdCredential);
- Assert.assertArrayEquals(password.toCharArray(), clearPwdCredential.getPassword());
+ if (token != null) {
+ MatcherAssert.assertThat(credential, CoreMatchers.instanceOf(BearerTokenCredential.class));
+ String obtainedToken = ((BearerTokenCredential) credential).getToken();
+ Assert.assertNotNull(obtainedToken);
+ Assert.assertEquals(obtainedToken, token);
+ } else {
+ MatcherAssert.assertThat(credential, CoreMatchers.instanceOf(PasswordCredential.class));
+ ClearPassword clearPwdCredential = ((PasswordCredential) credential).getPassword().castAs(ClearPassword.class);
+ Assert.assertNotNull(clearPwdCredential);
+ Assert.assertArrayEquals(password.toCharArray(), clearPwdCredential.getPassword());
+ }
} else if (callback instanceof AuthorizeCallback) {
- if(username.equals(((AuthorizeCallback) callback).getAuthenticationID()) &&
- username.equals(((AuthorizeCallback) callback).getAuthorizationID())) {
+ if (token != null) {
((AuthorizeCallback) callback).setAuthorized(true);
} else {
- ((AuthorizeCallback) callback).setAuthorized(false);
+ if (username.equals(((AuthorizeCallback) callback).getAuthenticationID()) &&
+ username.equals(((AuthorizeCallback) callback).getAuthorizationID())) {
+ ((AuthorizeCallback) callback).setAuthorized(true);
+ } else {
+ ((AuthorizeCallback) callback).setAuthorized(false);
+ }
}
} else if (callback instanceof CachedIdentityAuthorizeCallback) {
CachedIdentityAuthorizeCallback ciac = (CachedIdentityAuthorizeCallback) callback;
- if(ciac.getAuthorizationPrincipal() != null &&
+ if (ciac.getAuthorizationPrincipal() != null &&
username.equals(ciac.getAuthorizationPrincipal().getName())) {
ciac.setAuthorized(mockSecurityIdentity(ciac.getAuthorizationPrincipal()));
} else if (ciac.getIdentity() != null && username.equals(ciac.getIdentity().getPrincipal().getName())) {
@@ -508,6 +553,9 @@ protected CallbackHandler getCallbackHandler(String username, String realm, Stri
} else {
ciac.setAuthorized(null);
}
+ } else if (callback instanceof PrincipalAuthorizeCallback){
+ PrincipalAuthorizeCallback pac = (PrincipalAuthorizeCallback) callback;
+ pac.setAuthorized(true);
} else {
throw new UnsupportedCallbackException(callback);
}
diff --git a/tests/base/src/test/java/org/wildfly/security/ldap/DirContextFactoryRule.java b/tests/base/src/test/java/org/wildfly/security/ldap/DirContextFactoryRule.java
index 8917941078b..d1dac5e6bca 100644
--- a/tests/base/src/test/java/org/wildfly/security/ldap/DirContextFactoryRule.java
+++ b/tests/base/src/test/java/org/wildfly/security/ldap/DirContextFactoryRule.java
@@ -94,7 +94,6 @@ private static void createStores(KeyStore localhostKeyStore, KeyStore scarabKeyS
.addExtension(false, "BasicConstraints", "CA:true,pathlen:2147483647")
.build();
X509Certificate issuerCertificate = issuerSelfSignedX509CertificateAndSigningKey.getSelfSignedCertificate();
- localhostKeyStore.setCertificateEntry("ca", issuerCertificate);
trustStore.setCertificateEntry("mykey", issuerCertificate);
// Generates certificate and keystore for Localhost
diff --git a/tests/base/src/test/java/org/wildfly/security/ldap/ModifiabilitySuiteChild.java b/tests/base/src/test/java/org/wildfly/security/ldap/ModifiabilitySuiteChild.java
index a42a8b3074e..0b29b35b41d 100644
--- a/tests/base/src/test/java/org/wildfly/security/ldap/ModifiabilitySuiteChild.java
+++ b/tests/base/src/test/java/org/wildfly/security/ldap/ModifiabilitySuiteChild.java
@@ -72,7 +72,7 @@ public static void createRealm() throws InvalidNameException {
AttributeMapping.fromIdentity().from("sn").to("lastName").build(),
AttributeMapping.fromIdentity().from("description").to("description").build(),
AttributeMapping.fromIdentity().from("telephoneNumber").to("phones").build(),
- AttributeMapping.fromFilter("(&(objectClass=groupOfNames)(member={0}))").searchDn("ou=Finance,dc=elytron,dc=wildfly,dc=org").extractRdn("OU").to("businessArea").build())
+ AttributeMapping.fromFilter("(&(objectClass=groupOfNames)(member=uid={0}))").searchDn("ou=Finance,dc=elytron,dc=wildfly,dc=org").extractRdn("OU").to("businessArea").build())
.setNewIdentityParent(new LdapName("dc=elytron,dc=wildfly,dc=org"))
.setNewIdentityAttributes(attributes)
.setIteratorFilter("(uid=*)")
diff --git a/tests/base/src/test/java/org/wildfly/security/ldap/PrincipalMappingSuiteChild.java b/tests/base/src/test/java/org/wildfly/security/ldap/PrincipalMappingSuiteChild.java
index 433ad165696..5a4bfa775fb 100644
--- a/tests/base/src/test/java/org/wildfly/security/ldap/PrincipalMappingSuiteChild.java
+++ b/tests/base/src/test/java/org/wildfly/security/ldap/PrincipalMappingSuiteChild.java
@@ -36,7 +36,7 @@
public class PrincipalMappingSuiteChild {
@Test
- public void testSimpleToDn() throws RealmUnavailableException {
+ public void testLdapRealmPrincipalMapping() throws RealmUnavailableException {
SecurityRealm realm = LdapSecurityRealmBuilder.builder()
.setDirContextSupplier(LdapTestSuite.dirContextFactory.create())
.identityMapping()
@@ -68,40 +68,6 @@ public void testDnToSimple() throws RealmUnavailableException {
assertFalse("Exists", identity.exists());
}
- @Test
- public void testSimpleToSimpleValidate() throws RealmUnavailableException {
- SecurityRealm realm = LdapSecurityRealmBuilder.builder()
- .setDirContextSupplier(LdapTestSuite.dirContextFactory.create())
- .identityMapping()
- .setSearchDn("dc=elytron,dc=wildfly,dc=org")
- .setRdnIdentifier("uid")
- .build()
- .build();
-
- RealmIdentity identity = realm.getRealmIdentity(new NamePrincipal("PlainUser"));
- assertTrue("Exists", identity.exists());
-
- identity = realm.getRealmIdentity(new NamePrincipal("nobody"));
- assertFalse("Exists", identity.exists());
- }
-
- @Test
- public void testSimpleToSimpleReload() throws RealmUnavailableException {
- SecurityRealm realm = LdapSecurityRealmBuilder.builder()
- .setDirContextSupplier(LdapTestSuite.dirContextFactory.create())
- .identityMapping()
- .setSearchDn("dc=elytron,dc=wildfly,dc=org")
- .setRdnIdentifier("uid")
- .build()
- .build();
-
- RealmIdentity identity = realm.getRealmIdentity(new NamePrincipal("PlainUser"));
- assertTrue("Exists", identity.exists());
-
- identity = realm.getRealmIdentity(new NamePrincipal("nobody"));
- assertFalse("Exists", identity.exists());
- }
-
@Test
public void testDnToDnNoLookup() throws RealmUnavailableException {
SecurityRealm realm = LdapSecurityRealmBuilder.builder()
diff --git a/tests/base/src/test/java/org/wildfly/security/sasl/digest/CompatibilityServerTest.java b/tests/base/src/test/java/org/wildfly/security/sasl/digest/CompatibilityServerTest.java
index 3258ded2825..42de9f5290c 100644
--- a/tests/base/src/test/java/org/wildfly/security/sasl/digest/CompatibilityServerTest.java
+++ b/tests/base/src/test/java/org/wildfly/security/sasl/digest/CompatibilityServerTest.java
@@ -136,12 +136,7 @@ public void testRfc2831example2() throws Exception {
assertEquals("chris", server.getAuthorizationID());
}
-
- /**
- * Test with authorization ID (authzid) of other user
- */
- @Test
- public void testUnauthorizedAuthorizationId() throws Exception {
+ private SaslServer testCommonInitSaslServer() throws Exception {
mockNonce("OA9BSXrbuRhWay");
Map serverProps = new HashMap();
@@ -159,6 +154,18 @@ public void testUnauthorizedAuthorizationId() throws Exception {
assertEquals("realm=\"elwood.innosoft.com\",nonce=\"OA9BSXrbuRhWay\",charset=utf-8,algorithm=md5-sess", new String(message1, "UTF-8"));
assertFalse(server.isComplete());
+ return server;
+ }
+
+
+ //********************************************** */
+ /**
+ * Test with authorization ID (authzid) of other user
+ */
+ @Test
+ public void testUnauthorizedAuthorizationId() throws Exception {
+ SaslServer server = testCommonInitSaslServer();
+
byte[] message2 = "charset=utf-8,username=\"chris\",realm=\"elwood.innosoft.com\",nonce=\"OA9BSXrbuRhWay\",nc=00000001,cnonce=\"OA9BSuZWMSpW8m\",digest-uri=\"acap/elwood.innosoft.com\",maxbuf=65536,response=0d071450228e395e2c0999e02b6aa665,qop=auth,authzid=\"george\"".getBytes(StandardCharsets.UTF_8);
try {
@@ -560,22 +567,7 @@ public void testQopAuthConfRc440() throws Exception {
*/
@Test
public void testReplayAttack() throws Exception {
- mockNonce("OA9BSXrbuRhWay");
-
- Map serverProps = new HashMap();
- serverProps.put(REALM_PROPERTY, "elwood.innosoft.com");
- SaslServer server =
- new SaslServerBuilder(DigestServerFactory.class, SaslMechanismInformation.Names.DIGEST_MD5)
- .setUserName("chris")
- .setPassword(ClearPassword.ALGORITHM_CLEAR, new ClearPasswordSpec("secret".toCharArray()))
- .setProtocol("acap").setServerName("elwood.innosoft.com")
- .setProperties(serverProps)
- .build();
- assertFalse(server.isComplete());
-
- byte[] message1 = server.evaluateResponse(new byte[0]);
- assertEquals("realm=\"elwood.innosoft.com\",nonce=\"OA9BSXrbuRhWay\",charset=utf-8,algorithm=md5-sess", new String(message1, "UTF-8"));
- assertFalse(server.isComplete());
+ SaslServer server = testCommonInitSaslServer();
byte[] message2 = "charset=utf-8,username=\"chris\",realm=\"elwood.innosoft.com\",nonce=\"OA6MG9tEQGm2hh\",nc=00000001,cnonce=\"OA6MHXh6VqTrRk\",digest-uri=\"imap/elwood.innosoft.com\",response=d388dad90d4bbd760a152321f2143af7,qop=auth".getBytes(StandardCharsets.UTF_8);
try{
@@ -591,22 +583,7 @@ public void testReplayAttack() throws Exception {
*/
@Test
public void testBadResponse() throws Exception {
- mockNonce("OA9BSXrbuRhWay");
-
- Map serverProps = new HashMap();
- serverProps.put(REALM_PROPERTY, "elwood.innosoft.com");
- SaslServer server =
- new SaslServerBuilder(DigestServerFactory.class, SaslMechanismInformation.Names.DIGEST_MD5)
- .setUserName("chris")
- .setPassword(ClearPassword.ALGORITHM_CLEAR, new ClearPasswordSpec("secret".toCharArray()))
- .setProtocol("acap").setServerName("elwood.innosoft.com")
- .setProperties(serverProps)
- .build();
- assertFalse(server.isComplete());
-
- byte[] message1 = server.evaluateResponse(new byte[0]);
- assertEquals("realm=\"elwood.innosoft.com\",nonce=\"OA9BSXrbuRhWay\",charset=utf-8,algorithm=md5-sess", new String(message1, "UTF-8"));
- assertFalse(server.isComplete());
+ SaslServer server = testCommonInitSaslServer();
byte[] message2 = "charset=utf-8,username=\"chris\",realm=\"elwood.innosoft.com\",nonce=\"OA9BSXrbuRhWay\",nc=00000001,cnonce=\"OA9BSuZWMSpW8m\",digest-uri=\"acap/elwood.innosoft.com\",response=d388dad90d4bbd760a152321f2143af7,qop=auth".getBytes(StandardCharsets.UTF_8);
try{
@@ -743,4 +720,28 @@ public void testMoreRealmsWithEscapedDelimiters() throws Exception {
assertEquals("chris", server.getAuthorizationID());
}
+ @Test
+ public void testIncorrectResponseField() throws Exception {
+ mockNonce("OA6MG9tEQGm2hh");
+
+ SaslServer server =
+ new SaslServerBuilder(DigestServerFactory.class, SaslMechanismInformation.Names.DIGEST_MD5)
+ .setUserName("chris")
+ .setPassword(ClearPassword.ALGORITHM_CLEAR, new ClearPasswordSpec("secret".toCharArray()))
+ .setProtocol("imap").setServerName("elwood.innosoft.com")
+ .addMechanismRealm("elwood.innosoft.com")
+ .build();
+ assertFalse(server.isComplete());
+
+ byte[] message = server.evaluateResponse(new byte[0]);
+ assertEquals("realm=\"elwood.innosoft.com\",nonce=\"OA6MG9tEQGm2hh\",charset=utf-8,algorithm=md5-sess", new String(message, "UTF-8"));
+ assertFalse(server.isComplete());
+
+ byte[] invalidMessage = "charset=utf-8,username=\"chris\",realm=\"elwood.innosoft.com\",nonce=\"OA6MG9tEQGm2hh\",nc=00000001,cnonce=\"OA6MHXh6VqTrRk\",digest-uri=\"imap/elwood.innosoft.com\",response=incorrectResponse,qop=auth".getBytes(StandardCharsets.UTF_8);
+ try {
+ server.evaluateResponse(invalidMessage);
+ } catch (SaslException e) {
+ assertTrue(e.getMessage().contains("invalid proof"));
+ }
+ }
}
diff --git a/tests/base/src/test/java/org/wildfly/security/sasl/digest/DigestTest.java b/tests/base/src/test/java/org/wildfly/security/sasl/digest/DigestTest.java
index e8e62fa8d51..4ec12c09e1f 100644
--- a/tests/base/src/test/java/org/wildfly/security/sasl/digest/DigestTest.java
+++ b/tests/base/src/test/java/org/wildfly/security/sasl/digest/DigestTest.java
@@ -42,6 +42,7 @@
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.sasl.Sasl;
import javax.security.sasl.SaslClient;
+import javax.security.sasl.SaslException;
import javax.security.sasl.SaslServer;
import javax.security.sasl.SaslServerFactory;
@@ -146,14 +147,7 @@ public void testSuccessfulExchange() throws Exception {
CallbackHandler clientCallback = createClearPwdClientCallbackHandler("George", "gpwd", "TestRealm");
SaslClient client = Sasl.createSaslClient(new String[]{ DIGEST }, "George", "TestProtocol", "TestServer", Collections.emptyMap(), clientCallback);
- assertFalse(client.hasInitialResponse());
- byte[] message = server.evaluateResponse(new byte[0]);
- log.debug("Challenge:"+ new String(message, StandardCharsets.ISO_8859_1));
- message = client.evaluateChallenge(message);
- log.debug("Client response:"+ new String(message, StandardCharsets.ISO_8859_1));
- server.evaluateResponse(message);
- assertTrue(server.isComplete());
- assertEquals("George", server.getAuthorizationID());
+ assertExchange(server, client);
}
/**
* Test a successful exchange using the DIGEST mechanism but the default realm.
@@ -171,15 +165,7 @@ public void testSuccessfulExchange_DefaultRealm() throws Exception {
CallbackHandler clientCallback = createClearPwdClientCallbackHandler("George", "gpwd", null);
SaslClient client = Sasl.createSaslClient(new String[]{DIGEST}, "George", "TestProtocol", "TestServer", Collections.emptyMap(), clientCallback);
- assertFalse(client.hasInitialResponse());
- byte[] message = server.evaluateResponse(new byte[0]);
- log.debug("Challenge:"+ new String(message, StandardCharsets.ISO_8859_1));
- message = client.evaluateChallenge(message);
- log.debug("Client response:"+ new String(message, StandardCharsets.ISO_8859_1));
-
- server.evaluateResponse(message);
- assertTrue(server.isComplete());
- assertEquals("George", server.getAuthorizationID());
+ assertExchange(server, client);
}
/**
@@ -201,15 +187,7 @@ public void testSuccessfulExchange_AlternativeProtocol() throws Exception {
CallbackHandler clientCallback = createClearPwdClientCallbackHandler("George", "gpwd", null);
SaslClient client = Sasl.createSaslClient(new String[]{DIGEST}, "George", "OtherProtocol", "TestServer", Collections.emptyMap(), clientCallback);
- assertFalse(client.hasInitialResponse());
- byte[] message = server.evaluateResponse(new byte[0]);
- log.debug("Challenge:"+ new String(message, StandardCharsets.UTF_8));
- message = client.evaluateChallenge(message);
- log.debug("Client response:"+ new String(message, StandardCharsets.UTF_8));
-
- server.evaluateResponse(message);
- assertTrue(server.isComplete());
- assertEquals("George", server.getAuthorizationID());
+ assertExchange(server, client);
}
/**
@@ -318,14 +296,7 @@ public void testRealmSelection() throws Exception {
CallbackHandler clientCallback = createClearPwdClientCallbackHandler("George", "gpwd", "last\\ ");
SaslClient client = Sasl.createSaslClient(new String[]{DIGEST}, "George", "TestProtocol", "TestServer", Collections.emptyMap(), clientCallback);
- assertFalse(client.hasInitialResponse());
- byte[] message = server.evaluateResponse(new byte[0]);
- log.debug("Challenge:"+ new String(message, StandardCharsets.ISO_8859_1));
- message = client.evaluateChallenge(message);
- log.debug("Client response:" + new String(message, StandardCharsets.ISO_8859_1));
- server.evaluateResponse(message);
- assertTrue(server.isComplete());
- assertEquals("George", server.getAuthorizationID());
+ assertExchange(server, client);
}
/*
@@ -515,13 +486,7 @@ public void testSuccessfulExchange_PreHashedClient() throws Exception {
SaslClient client = Sasl.createSaslClient(new String[]{DIGEST}, "George", "TestProtocol", "TestServer", clientProps, clientCallback);
- assertFalse(client.hasInitialResponse());
- byte[] message = server.evaluateResponse(new byte[0]);
-
- message = client.evaluateChallenge(message);
- server.evaluateResponse(message);
- assertTrue(server.isComplete());
- assertEquals("George", server.getAuthorizationID());
+ assertExchange(server,client);
}
/**
@@ -544,13 +509,7 @@ public void testSuccessfulExchange_DefaultRealm_PreHashedClient() throws Excepti
clientProps.put(PRE_DIGESTED_PROPERTY, "true");
SaslClient client = Sasl.createSaslClient(new String[]{DIGEST}, "George", "TestProtocol", "TestServer", clientProps, clientCallback);
- assertFalse(client.hasInitialResponse());
- byte[] message = server.evaluateResponse(new byte[0]);
- message = client.evaluateChallenge(message);
-
- server.evaluateResponse(message);
- assertTrue(server.isComplete());
- assertEquals("George", server.getAuthorizationID());
+ assertExchange(server,client);
}
/**
@@ -753,14 +712,7 @@ public void testSuccessfulExchangeNullAuthorizationId() throws Exception {
CallbackHandler clientCallback = createClearPwdClientCallbackHandler("George", "gpwd", "TestRealm");
SaslClient client = Sasl.createSaslClient(new String[]{ DIGEST }, null, "TestProtocol", "TestServer", Collections.emptyMap(), clientCallback);
- assertFalse(client.hasInitialResponse());
- byte[] message = server.evaluateResponse(new byte[0]);
- log.debug("Challenge:"+ new String(message, StandardCharsets.ISO_8859_1));
- message = client.evaluateChallenge(message);
- log.debug("Client response:"+ new String(message, StandardCharsets.ISO_8859_1));
- server.evaluateResponse(message);
- assertTrue(server.isComplete());
- assertEquals("George", server.getAuthorizationID());
+ assertExchange(server, client);
}
/**
@@ -779,14 +731,7 @@ public void testSuccessfulExchangeEmptyAuthorizationId() throws Exception {
CallbackHandler clientCallback = createClearPwdClientCallbackHandler("George", "gpwd", "TestRealm");
SaslClient client = Sasl.createSaslClient(new String[]{ DIGEST }, "", "TestProtocol", "TestServer", Collections.emptyMap(), clientCallback);
- assertFalse(client.hasInitialResponse());
- byte[] message = server.evaluateResponse(new byte[0]);
- log.debug("Challenge:"+ new String(message, StandardCharsets.ISO_8859_1));
- message = client.evaluateChallenge(message);
- log.debug("Client response:"+ new String(message, StandardCharsets.ISO_8859_1));
- server.evaluateResponse(message);
- assertTrue(server.isComplete());
- assertEquals("George", server.getAuthorizationID());
+ assertExchange(server, client);
}
private KeySpec getDigestKeySpec(String username, String password, String realm) throws NoSuchAlgorithmException {
@@ -826,4 +771,14 @@ public void testUnboundServerName() throws Exception {
assertEquals("TestServer5", server.getNegotiatedProperty(Sasl.BOUND_SERVER_NAME));
}
+ private void assertExchange(SaslServer server, SaslClient client) throws SaslException {
+ assertFalse(client.hasInitialResponse());
+ byte[] message = server.evaluateResponse(new byte[0]);
+ log.debug("Challenge:"+ new String(message, StandardCharsets.ISO_8859_1));
+ message = client.evaluateChallenge(message);
+ log.debug("Client response:"+ new String(message, StandardCharsets.ISO_8859_1));
+ server.evaluateResponse(message);
+ assertTrue(server.isComplete());
+ assertEquals("George", server.getAuthorizationID());
+ }
}
diff --git a/tests/base/src/test/java/org/wildfly/security/sasl/gssapi/TestKDC.java b/tests/base/src/test/java/org/wildfly/security/sasl/gssapi/TestKDC.java
index 88789b58e58..23fd54723d0 100644
--- a/tests/base/src/test/java/org/wildfly/security/sasl/gssapi/TestKDC.java
+++ b/tests/base/src/test/java/org/wildfly/security/sasl/gssapi/TestKDC.java
@@ -26,7 +26,6 @@
import java.util.Map;
import org.apache.directory.api.ldap.model.entry.DefaultEntry;
-import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
import org.apache.directory.api.ldap.model.ldif.LdifEntry;
import org.apache.directory.api.ldap.model.ldif.LdifReader;
import org.apache.directory.api.ldap.model.schema.SchemaManager;
@@ -37,21 +36,21 @@
import org.apache.directory.server.core.factory.DirectoryServiceFactory;
import org.apache.directory.server.core.factory.PartitionFactory;
import org.apache.directory.server.core.kerberos.KeyDerivationInterceptor;
-import org.apache.directory.server.kerberos.KerberosConfig;
-import org.apache.directory.server.kerberos.kdc.KdcServer;
import org.apache.directory.server.kerberos.shared.crypto.encryption.KerberosKeyFactory;
-import org.apache.directory.server.kerberos.shared.keytab.Keytab;
-import org.apache.directory.server.kerberos.shared.keytab.KeytabEntry;
import org.apache.directory.server.ldap.LdapServer;
import org.apache.directory.server.protocol.shared.transport.TcpTransport;
import org.apache.directory.server.protocol.shared.transport.Transport;
-import org.apache.directory.server.protocol.shared.transport.UdpTransport;
-import org.apache.directory.shared.kerberos.KerberosTime;
import org.apache.directory.shared.kerberos.codec.types.EncryptionType;
import org.apache.directory.shared.kerberos.components.EncryptionKey;
+import org.apache.kerby.kerberos.kerb.KrbException;
+import org.apache.kerby.kerberos.kerb.keytab.Keytab;
+import org.apache.kerby.kerberos.kerb.keytab.KeytabEntry;
+import org.apache.kerby.kerberos.kerb.server.SimpleKdcServer;
+import org.apache.kerby.kerberos.kerb.server.impl.DefaultInternalKdcServerImpl;
+import org.apache.kerby.kerberos.kerb.type.KerberosTime;
+import org.apache.kerby.kerberos.kerb.type.base.PrincipalName;
import org.jboss.logging.Logger;
-import javax.security.auth.kerberos.KerberosPrincipal;
/**
* Utility class to wrap starting and stopping of the directory server and the KDC.
@@ -63,7 +62,7 @@ public class TestKDC {
private static Logger log = Logger.getLogger(TestKDC.class);
private File workingDir;
private DirectoryService directoryService;
- private KdcServer kdcServer;
+ private SimpleKdcServer kdcServer;
private String originalConfig;
private boolean exposeLdapServer;
private LdapServer ldapServer;
@@ -117,7 +116,6 @@ private static void createPartition(final DirectoryServiceFactory dsf, final Sch
for (String current : indexAttributes) {
pf.addIndex(p, current, 10);
}
- p.setCacheService(directoryService.getCacheService());
p.initialize();
directoryService.addPartition(p);
}
@@ -156,27 +154,24 @@ public void startKDC() {
File configPath = new File(TestKDC.class.getResource("/krb5.conf").getFile());
originalConfig = System.setProperty("java.security.krb5.conf", configPath.getAbsolutePath());
- KdcServer kdcServer = new KdcServer();
- kdcServer.setServiceName("TestKDCServer");
- kdcServer.setSearchBaseDn("dc=wildfly,dc=org");
- KerberosConfig config = kdcServer.getConfig();
- config.setServicePrincipal("krbtgt/WILDFLY.ORG@WILDFLY.ORG");
- config.setPrimaryRealm("WILDFLY.ORG");
- config.setMaximumTicketLifetime(60000 * 1440);
- config.setMaximumRenewableLifetime(60000 * 10080);
-
- config.setPaEncTimestampRequired(false);
+ try {
+ SimpleKdcServer kdcServer = new SimpleKdcServer();
+ kdcServer.setKdcRealm("WILDFLY.ORG");
+ kdcServer.setKdcHost("localhost");
+ kdcServer.setInnerKdcImpl(new DefaultInternalKdcServerImpl(kdcServer.getKdcSetting()));
+ kdcServer.setAllowUdp(true);
+ kdcServer.setKdcUdpPort(6088);
- UdpTransport udp = new UdpTransport("localhost", 6088);
- kdcServer.addTransports(udp);
+ kdcServer.init();
- kdcServer.setDirectoryService(directoryService);
+ kdcServer.createPrincipal("sasl/test_server_1@WILDFLY.ORG", "servicepwd");
+ kdcServer.createPrincipal("sasl/test_server_2@WILDFLY.ORG", "servicepwd");
+ kdcServer.createPrincipal("jduke@WILDFLY.ORG", "theduke");
- // Launch the server
- try {
+ // Launch the server
kdcServer.start();
this.kdcServer = kdcServer;
- } catch (IOException | LdapInvalidDnException e) {
+ } catch (KrbException e) {
throw new IllegalStateException("Unable to start KDC", e);
}
}
@@ -186,7 +181,11 @@ private void stopKDC() {
return;
}
- kdcServer.stop();
+ try {
+ kdcServer.stop();
+ } catch (KrbException e) {
+ throw new IllegalStateException("Unable to stop KDC", e);
+ }
kdcServer = null;
if (originalConfig != null) {
@@ -232,7 +231,7 @@ public void stopAll() {
public String generateKeyTab(String keyTabFileName, String... credentials) {
log.debug("Generating keytab: " + keyTabFileName);
List entries = new ArrayList<>();
- KerberosTime ktm = new KerberosTime();
+ KerberosTime ktm = KerberosTime.now();
for (int i = 0; i < credentials.length;) {
String principal = credentials[i++];
@@ -242,15 +241,16 @@ public String generateKeyTab(String keyTabFileName, String... credentials) {
.entrySet()) {
EncryptionKey key = keyEntry.getValue();
log.debug("Adding key=" + key + " for principal=" + principal);
- entries.add(new KeytabEntry(principal, KerberosPrincipal.KRB_NT_PRINCIPAL, ktm, (byte) key.getKeyVersion(), key));
+ entries.add(new KeytabEntry(new PrincipalName(principal), ktm, key.getKeyVersion(),
+ new org.apache.kerby.kerberos.kerb.type.base.EncryptionKey(key.getKeyType().getValue(), key.getKeyValue(), key.getKeyVersion())));
}
}
- Keytab keyTab = Keytab.getInstance();
- keyTab.setEntries(entries);
+ Keytab keyTab = new Keytab();
+ keyTab.addKeytabEntries(entries);
try {
File keyTabFile = new File(workingDir, keyTabFileName);
- keyTab.write(keyTabFile);
+ keyTab.store(keyTabFile);
return keyTabFile.getAbsolutePath();
} catch (IOException e) {
throw new IllegalStateException("Cannot create keytab: " + keyTabFileName, e);
diff --git a/tests/base/src/test/java/org/wildfly/security/sasl/oauth2/OAuth2SaslClientV10Test.java b/tests/base/src/test/java/org/wildfly/security/sasl/oauth2/OAuth2SaslClientV10Test.java
index 86edcb5fc06..f8370ce9944 100644
--- a/tests/base/src/test/java/org/wildfly/security/sasl/oauth2/OAuth2SaslClientV10Test.java
+++ b/tests/base/src/test/java/org/wildfly/security/sasl/oauth2/OAuth2SaslClientV10Test.java
@@ -101,54 +101,12 @@ public static void onAfter() throws Exception {
@Test
public void testWithResourceOwnerCredentialsUsingConfiguration() throws Exception {
- URI serverUri = URI.create("protocol://test1.org");
- SaslClient saslClient = createSaslClientFromConfiguration(serverUri);
-
- assertNotNull("OAuth2SaslClient is null", saslClient);
-
- SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER)
- .setServerName("resourceserver.comn")
- .setProtocol("imap")
- .addRealm("oauth-realm", createSecurityRealmMock())
- .setDefaultRealmName("oauth-realm")
- .build();
-
- byte[] message = AbstractSaslParticipant.NO_BYTES;
-
- do {
- message = saslClient.evaluateChallenge(message);
- if (message == null) break;
- message = saslServer.evaluateResponse(message);
- } while (message != null);
-
- assertTrue(saslServer.isComplete());
- assertTrue(saslClient.isComplete());
+ testWithSaslClientAndServer("protocol://test1.org");
}
@Test
public void testWithClientCredentialsUsingConfiguration() throws Exception {
- URI serverUri = URI.create("protocol://test2.org");
- SaslClient saslClient = createSaslClientFromConfiguration(serverUri);
-
- assertNotNull("OAuth2SaslClient is null", saslClient);
-
- SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER)
- .setServerName("resourceserver.comn")
- .setProtocol("imap")
- .addRealm("oauth-realm", createSecurityRealmMock())
- .setDefaultRealmName("oauth-realm")
- .build();
-
- byte[] message = AbstractSaslParticipant.NO_BYTES;
-
- do {
- message = saslClient.evaluateChallenge(message);
- if (message == null) break;
- message = saslServer.evaluateResponse(message);
- } while (message != null);
-
- assertTrue(saslServer.isComplete());
- assertTrue(saslClient.isComplete());
+ testWithSaslClientAndServer("protocol://test2.org");
}
@Test
@@ -204,28 +162,7 @@ public void failedInvalidClientCredentialsUsingConfiguration() throws Exception
@Test
public void testWithResourceOwnerCredentials() throws Exception {
- URI serverUri = URI.create("protocol://test5.org");
- SaslClient saslClient = createSaslClientFromConfiguration(serverUri);
-
- assertNotNull("OAuth2SaslClient is null", saslClient);
-
- SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER)
- .setServerName("resourceserver.comn")
- .setProtocol("imap")
- .addRealm("oauth-realm", createSecurityRealmMock())
- .setDefaultRealmName("oauth-realm")
- .build();
-
- byte[] message = AbstractSaslParticipant.NO_BYTES;
-
- do {
- message = saslClient.evaluateChallenge(message);
- if (message == null) break;
- message = saslServer.evaluateResponse(message);
- } while (message != null);
-
- assertTrue(saslServer.isComplete());
- assertTrue(saslClient.isComplete());
+ testWithSaslClientAndServer("protocol://test5.org");
}
@Test
@@ -496,4 +433,28 @@ private SaslClient createSaslClientFromConfiguration(URI serverUri) throws SaslE
AuthenticationConfiguration authenticationConfiguration = contextConfigurationClient.getAuthenticationConfiguration(serverUri, context);
return contextConfigurationClient.createSaslClient(serverUri, authenticationConfiguration, Arrays.asList(SaslMechanismInformation.Names.OAUTHBEARER));
}
+
+ private void testWithSaslClientAndServer(String serverUri) throws Exception {
+ SaslClient saslClient = createSaslClientFromConfiguration(URI.create(serverUri));
+
+ assertNotNull("OAuth2SaslClient is null", saslClient);
+
+ SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER)
+ .setServerName("resourceserver.comn")
+ .setProtocol("imap")
+ .addRealm("oauth-realm", createSecurityRealmMock())
+ .setDefaultRealmName("oauth-realm")
+ .build();
+
+ byte[] message = AbstractSaslParticipant.NO_BYTES;
+
+ do {
+ message = saslClient.evaluateChallenge(message);
+ if (message == null) break;
+ message = saslServer.evaluateResponse(message);
+ } while (message != null);
+
+ assertTrue(saslServer.isComplete());
+ assertTrue(saslClient.isComplete());
+ }
}
diff --git a/tests/base/src/test/java/org/wildfly/security/sasl/oauth2/OAuth2SaslClientV11Test.java b/tests/base/src/test/java/org/wildfly/security/sasl/oauth2/OAuth2SaslClientV11Test.java
index 86162b3530d..7921694da61 100644
--- a/tests/base/src/test/java/org/wildfly/security/sasl/oauth2/OAuth2SaslClientV11Test.java
+++ b/tests/base/src/test/java/org/wildfly/security/sasl/oauth2/OAuth2SaslClientV11Test.java
@@ -130,54 +130,12 @@ public static void tearDown() throws Exception {
@Test
public void testWithResourceOwnerCredentialsUsingConfiguration() throws Exception {
- URI serverUri = URI.create("protocol://test1.org");
- SaslClient saslClient = createSaslClientFromConfiguration(serverUri);
-
- assertNotNull("OAuth2SaslClient is null", saslClient);
-
- SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER)
- .setServerName("resourceserver.comn")
- .setProtocol("imap")
- .addRealm("oauth-realm", createSecurityRealmMock())
- .setDefaultRealmName("oauth-realm")
- .build();
-
- byte[] message = AbstractSaslParticipant.NO_BYTES;
-
- do {
- message = saslClient.evaluateChallenge(message);
- if (message == null) break;
- message = saslServer.evaluateResponse(message);
- } while (message != null);
-
- assertTrue(saslServer.isComplete());
- assertTrue(saslClient.isComplete());
+ testWithSaslClientAndServer("protocol://test1.org");
}
@Test
public void testWithClientCredentialsUsingConfiguration() throws Exception {
- URI serverUri = URI.create("protocol://test2.org");
- SaslClient saslClient = createSaslClientFromConfiguration(serverUri);
-
- assertNotNull("OAuth2SaslClient is null", saslClient);
-
- SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER)
- .setServerName("resourceserver.comn")
- .setProtocol("imap")
- .addRealm("oauth-realm", createSecurityRealmMock())
- .setDefaultRealmName("oauth-realm")
- .build();
-
- byte[] message = AbstractSaslParticipant.NO_BYTES;
-
- do {
- message = saslClient.evaluateChallenge(message);
- if (message == null) break;
- message = saslServer.evaluateResponse(message);
- } while (message != null);
-
- assertTrue(saslServer.isComplete());
- assertTrue(saslClient.isComplete());
+ testWithSaslClientAndServer("protocol://test2.org");
}
@Test
@@ -259,27 +217,7 @@ public void testWithResourceOwnerCredentials() throws Exception {
@Test
public void testWithBearerTokenFromConfiguration() throws Exception {
- SaslClient saslClient = createSaslClientFromConfiguration(URI.create("protocol://test5.org"));
-
- assertNotNull("OAuth2SaslClient is null", saslClient);
-
- SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER)
- .setServerName("resourceserver.comn")
- .setProtocol("imap")
- .addRealm("oauth-realm", createSecurityRealmMock())
- .setDefaultRealmName("oauth-realm")
- .build();
-
- byte[] message = AbstractSaslParticipant.NO_BYTES;
-
- do {
- message = saslClient.evaluateChallenge(message);
- if (message == null) break;
- message = saslServer.evaluateResponse(message);
- } while (message != null);
-
- assertTrue(saslServer.isComplete());
- assertTrue(saslClient.isComplete());
+ testWithSaslClientAndServer("protocol://test5.org");
}
@Test
@@ -473,34 +411,11 @@ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallback
@Test
public void testWithResourceOwnerCredentialsInCredentialStoreUsingConfiguration() throws Exception {
- URI serverUri = URI.create("protocol://test8.org");
- SaslClient saslClient = createSaslClientFromConfiguration(serverUri);
-
- assertNotNull("OAuth2SaslClient is null", saslClient);
-
- SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER)
- .setServerName("resourceserver.comn")
- .setProtocol("imap")
- .addRealm("oauth-realm", createSecurityRealmMock())
- .setDefaultRealmName("oauth-realm")
- .build();
-
- byte[] message = AbstractSaslParticipant.NO_BYTES;
-
- do {
- message = saslClient.evaluateChallenge(message);
- if (message == null) break;
- message = saslServer.evaluateResponse(message);
- } while (message != null);
-
- assertTrue(saslServer.isComplete());
- assertTrue(saslClient.isComplete());
+ testWithSaslClientAndServer("protocol://test8.org");
}
- @Test
- public void failedResourceOwnerCredentialsUsingConfiguration() throws Exception {
- SaslClient saslClient = createSaslClientFromConfiguration(URI.create("protocol://test9.org"));
-
+ public void testInvalidCredentialsUsingConfiguration(String serverURI) throws Exception {
+ SaslClient saslClient = createSaslClientFromConfiguration(URI.create(serverURI));
assertNotNull("OAuth2SaslClient is null", saslClient);
SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER)
@@ -525,57 +440,18 @@ public void failedResourceOwnerCredentialsUsingConfiguration() throws Exception
}
@Test
- public void failedResourceOwnerCredentialsFromCredentialStoreUsingConfiguration() throws Exception {
- SaslClient saslClient = createSaslClientFromConfiguration(URI.create("protocol://test10.org"));
-
- assertNotNull("OAuth2SaslClient is null", saslClient);
-
- SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER)
- .setServerName("resourceserver.comn")
- .setProtocol("imap")
- .addRealm("oauth-realm", createSecurityRealmMock())
- .setDefaultRealmName("oauth-realm")
- .build();
-
- byte[] message = AbstractSaslParticipant.NO_BYTES;
+ public void failedResourceOwnerCredentialsUsingConfiguration() throws Exception {
+ testInvalidCredentialsUsingConfiguration("protocol://test9.org");
+ }
- try {
- do {
- message = saslClient.evaluateChallenge(message);
- if (message == null) break;
- message = saslServer.evaluateResponse(message);
- } while (message != null);
- fail("Expected bad response from server");
- } catch (Exception e) {
- e.printStackTrace();
- }
+ @Test
+ public void failedResourceOwnerCredentialsFromCredentialStoreUsingConfiguration() throws Exception {
+ testInvalidCredentialsUsingConfiguration("protocol://test10.org");
}
@Test
public void failedClientCredentialsFromCredentialStoreUsingConfiguration() throws Exception {
- SaslClient saslClient = createSaslClientFromConfiguration(URI.create("protocol://test11.org"));
-
- assertNotNull("OAuth2SaslClient is null", saslClient);
-
- SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER)
- .setServerName("resourceserver.comn")
- .setProtocol("imap")
- .addRealm("oauth-realm", createSecurityRealmMock())
- .setDefaultRealmName("oauth-realm")
- .build();
-
- byte[] message = AbstractSaslParticipant.NO_BYTES;
-
- try {
- do {
- message = saslClient.evaluateChallenge(message);
- if (message == null) break;
- message = saslServer.evaluateResponse(message);
- } while (message != null);
- fail("Expected bad response from server");
- } catch (Exception e) {
- e.printStackTrace();
- }
+ testInvalidCredentialsUsingConfiguration("protocol://test11.org");
}
private SecurityRealm createSecurityRealmMock() throws MalformedURLException {
@@ -620,4 +496,28 @@ private SaslClient createSaslClientFromConfiguration(URI serverUri) throws SaslE
AuthenticationConfiguration authenticationConfiguration = contextConfigurationClient.getAuthenticationConfiguration(serverUri, context);
return contextConfigurationClient.createSaslClient(serverUri, authenticationConfiguration, Collections.singletonList(SaslMechanismInformation.Names.OAUTHBEARER));
}
+
+ private void testWithSaslClientAndServer(String serverUri) throws Exception {
+ SaslClient saslClient = createSaslClientFromConfiguration(URI.create(serverUri));
+
+ assertNotNull("OAuth2SaslClient is null", saslClient);
+
+ SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER)
+ .setServerName("resourceserver.comn")
+ .setProtocol("imap")
+ .addRealm("oauth-realm", createSecurityRealmMock())
+ .setDefaultRealmName("oauth-realm")
+ .build();
+
+ byte[] message = AbstractSaslParticipant.NO_BYTES;
+
+ do {
+ message = saslClient.evaluateChallenge(message);
+ if (message == null) break;
+ message = saslServer.evaluateResponse(message);
+ } while (message != null);
+
+ assertTrue(saslServer.isComplete());
+ assertTrue(saslClient.isComplete());
+ }
}
diff --git a/tests/base/src/test/java/org/wildfly/security/sasl/otp/OTPTest.java b/tests/base/src/test/java/org/wildfly/security/sasl/otp/OTPTest.java
index 69da63105c3..6ab00211f03 100644
--- a/tests/base/src/test/java/org/wildfly/security/sasl/otp/OTPTest.java
+++ b/tests/base/src/test/java/org/wildfly/security/sasl/otp/OTPTest.java
@@ -819,106 +819,18 @@ public void testAuthenticationWithInvalidPassPhrase() throws Exception {
@Test
public void testAuthenticationWithLongSeed() throws Exception {
- final String algorithm = ALGORITHM_OTP_MD5;
- final SaslClientFactory clientFactory = obtainSaslClientFactory(OTPSaslClientFactory.class);
- assertNotNull(clientFactory);
-
- PasswordFactory passwordFactory = PasswordFactory.getInstance(algorithm);
- final Password password = passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("505d889f90085847").hexDecode().drain(),
- "thisSeedIsTooLong", 500));
- final SaslServerBuilder.BuilderReference securityDomainReference = new SaslServerBuilder.BuilderReference<>();
- final SaslServerBuilder.BuilderReference closeableReference = new SaslServerBuilder.BuilderReference<>();
- try {
- final SaslServer saslServer = createSaslServer(password, closeableReference, securityDomainReference);
-
- final CallbackHandler cbh = createClientCallbackHandler("userName", "This is a test.", PASS_PHRASE, algorithm, HEX_RESPONSE);
- final SaslClient saslClient = clientFactory.createSaslClient(new String[]{SaslMechanismInformation.Names.OTP}, null, "test", "testserver1.example.com",
- Collections.emptyMap(), cbh);
-
- byte[] message = saslClient.evaluateChallenge(new byte[0]);
- try {
- saslServer.evaluateResponse(message);
- fail("Expected SaslException not thrown");
- } catch (SaslException expected) {
- }
- saslClient.dispose();
- saslServer.dispose();
-
- // The password should remain unchanged
- checkPassword(securityDomainReference, "userName", (OneTimePassword) password, algorithm);
- } finally {
- closeableReference.getReference().close();
- }
+ testPasswordRemainsUnchanged("thisSeedIsTooLong", 500);
}
@Test
public void testAuthenticationWithNonAlphanumericSeed() throws Exception {
- final String algorithm = ALGORITHM_OTP_MD5;
-
- final SaslClientFactory clientFactory = obtainSaslClientFactory(OTPSaslClientFactory.class);
- assertNotNull(clientFactory);
-
- PasswordFactory passwordFactory = PasswordFactory.getInstance(algorithm);
- final Password password = passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("505d889f90085847").hexDecode().drain(),
- "A seed!", 500));
- final SaslServerBuilder.BuilderReference securityDomainReference = new SaslServerBuilder.BuilderReference<>();
- final SaslServerBuilder.BuilderReference closeableReference = new SaslServerBuilder.BuilderReference<>();
- try {
- final SaslServer saslServer = createSaslServer(password, closeableReference, securityDomainReference);
-
- final CallbackHandler cbh = createClientCallbackHandler("userName", "This is a test.", PASS_PHRASE, algorithm, HEX_RESPONSE);
- final SaslClient saslClient = clientFactory.createSaslClient(new String[]{SaslMechanismInformation.Names.OTP}, null, "test", "testserver1.example.com",
- Collections.emptyMap(), cbh);
-
- byte[] message = saslClient.evaluateChallenge(new byte[0]);
- try {
- saslServer.evaluateResponse(message);
- fail("Expected SaslException not thrown");
- } catch (SaslException expected) {
- }
- saslClient.dispose();
- saslServer.dispose();
-
- // The password should remain unchanged
- checkPassword(securityDomainReference, "userName", (OneTimePassword) password, algorithm);
- } finally {
- closeableReference.getReference().close();
- }
+ testPasswordRemainsUnchanged("A seed!", 500);
}
@Test
public void testAuthenticationWithInvalidSequenceNumber() throws Exception {
- final String algorithm = ALGORITHM_OTP_MD5;
- final SaslClientFactory clientFactory = obtainSaslClientFactory(OTPSaslClientFactory.class);
- assertNotNull(clientFactory);
-
- PasswordFactory passwordFactory = PasswordFactory.getInstance(algorithm);
- final Password password = passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("505d889f90085847").hexDecode().drain(),
- "ke1234", 0));
- final SaslServerBuilder.BuilderReference securityDomainReference = new SaslServerBuilder.BuilderReference<>();
- final SaslServerBuilder.BuilderReference closeableReference = new SaslServerBuilder.BuilderReference<>();
- try {
- final SaslServer saslServer = createSaslServer(password, closeableReference, securityDomainReference);
-
- final CallbackHandler cbh = createClientCallbackHandler("userName", "This is a test.", PASS_PHRASE, algorithm, HEX_RESPONSE);
- final SaslClient saslClient = clientFactory.createSaslClient(new String[] { SaslMechanismInformation.Names.OTP }, null, "test", "testserver1.example.com",
- Collections.emptyMap(), cbh);
-
- byte[] message = saslClient.evaluateChallenge(new byte[0]);
- try {
- saslServer.evaluateResponse(message);
- fail("Expected SaslException not thrown");
- } catch (SaslException expected) {
- }
- saslClient.dispose();
- saslServer.dispose();
-
- // The password should remain unchanged
- checkPassword(securityDomainReference, "userName", (OneTimePassword) password, algorithm);
- } finally {
- closeableReference.getReference().close();
- }
+ testPasswordRemainsUnchanged("ke1234", 0);
}
@Test
@@ -1082,6 +994,40 @@ private CallbackHandler createClientCallbackHandler(String username, String pass
return ClientUtils.getCallbackHandler(new URI("remote://localhost"), context);
}
+ private void testPasswordRemainsUnchanged(String seed, int sequenceNumber) throws Exception {
+ final String algorithm = ALGORITHM_OTP_MD5;
+ final SaslClientFactory clientFactory = obtainSaslClientFactory(OTPSaslClientFactory.class);
+ assertNotNull(clientFactory);
+
+ PasswordFactory passwordFactory = PasswordFactory.getInstance(algorithm);
+ final Password password = passwordFactory.generatePassword(new OneTimePasswordSpec(CodePointIterator.ofString("505d889f90085847").hexDecode().drain(),
+ seed, sequenceNumber));
+ final SaslServerBuilder.BuilderReference securityDomainReference = new SaslServerBuilder.BuilderReference<>();
+ final SaslServerBuilder.BuilderReference closeableReference = new SaslServerBuilder.BuilderReference<>();
+ try {
+ final SaslServer saslServer = createSaslServer(password, closeableReference, securityDomainReference);
+
+ final CallbackHandler cbh = createClientCallbackHandler("userName", "This is a test.", PASS_PHRASE, algorithm, HEX_RESPONSE);
+ final SaslClient saslClient = clientFactory.createSaslClient(new String[]{SaslMechanismInformation.Names.OTP}, null, "test", "testserver1.example.com",
+ Collections.emptyMap(), cbh);
+
+ byte[] message = saslClient.evaluateChallenge(new byte[0]);
+ try {
+ saslServer.evaluateResponse(message);
+ fail("Expected SaslException not thrown");
+ } catch (SaslException expected) {
+ }
+ saslClient.dispose();
+ saslServer.dispose();
+
+ // The password should remain unchanged
+ checkPassword(securityDomainReference, "userName", (OneTimePassword) password, algorithm);
+ } finally {
+ closeableReference.getReference().close();
+ }
+
+ }
+
private static final String[] ALTERNATE_DICTIONARY = new String[] {
"poel", "qewn", "xlob", "preg", "qome", "zarm", "sas",
"oerk", "sct", "seb", "ilan", "wct", "bp", "sft",
diff --git a/tests/base/src/test/java/org/wildfly/security/sasl/scram/BasicScramSelfTest.java b/tests/base/src/test/java/org/wildfly/security/sasl/scram/BasicScramSelfTest.java
index 0eeeb7a4d25..4c0933d6af0 100644
--- a/tests/base/src/test/java/org/wildfly/security/sasl/scram/BasicScramSelfTest.java
+++ b/tests/base/src/test/java/org/wildfly/security/sasl/scram/BasicScramSelfTest.java
@@ -92,46 +92,22 @@ public Void run() {
@Test
public void testAuthenticationSha1ClearPassword() throws Exception {
- final SaslServer saslServer =
- new SaslServerBuilder(ScramSaslServerFactory.class, SaslMechanismInformation.Names.SCRAM_SHA_1)
- .setUserName("user")
- .setPassword("pencil".toCharArray())
- .build();
- CallbackHandler clientHandler = createClientCallbackHandler("user", "pencil".toCharArray());
- testAuthentication(SaslMechanismInformation.Names.SCRAM_SHA_1, saslServer, clientHandler, "user", EMPTY);
+ performAuthenticationTest("user", "pencil", "user", "pencil");
}
@Test(expected = SaslException.class)
public void testAuthenticationSha1ClearPasswordBadUsername() throws Exception {
- final SaslServer saslServer =
- new SaslServerBuilder(ScramSaslServerFactory.class, SaslMechanismInformation.Names.SCRAM_SHA_1)
- .setUserName("user")
- .setPassword("pencil".toCharArray())
- .build();
- CallbackHandler clientHandler = createClientCallbackHandler("wrong", "pencil".toCharArray());
- testAuthentication(SaslMechanismInformation.Names.SCRAM_SHA_1, saslServer, clientHandler, "user", EMPTY);
+ performAuthenticationTest("user", "pencil", "wrong", "pencil");
}
@Test(expected = SaslException.class)
public void testAuthenticationSha1ClearPasswordBadPassword() throws Exception {
- final SaslServer saslServer =
- new SaslServerBuilder(ScramSaslServerFactory.class, SaslMechanismInformation.Names.SCRAM_SHA_1)
- .setUserName("user")
- .setPassword("pencil".toCharArray())
- .build();
- CallbackHandler clientHandler = createClientCallbackHandler("user", "wrong".toCharArray());
- testAuthentication(SaslMechanismInformation.Names.SCRAM_SHA_1, saslServer, clientHandler, "user", EMPTY);
+ performAuthenticationTest("user", "pencil", "user", "wrong");
}
@Test
public void testAuthenticationSha1ClearCredentialPassword() throws Exception {
- final SaslServer saslServer =
- new SaslServerBuilder(ScramSaslServerFactory.class, SaslMechanismInformation.Names.SCRAM_SHA_1)
- .setUserName("user")
- .setPassword("pencil".toCharArray())
- .build();
- CallbackHandler clientHandler = createClientCallbackHandler("user", "pencil".toCharArray());
- testAuthentication(SaslMechanismInformation.Names.SCRAM_SHA_1, saslServer, clientHandler, "user", EMPTY);
+ performAuthenticationTest("user", "pencil", "user", "pencil");
}
@Test
@@ -265,6 +241,17 @@ public void testPlusClientWithBindingWithNonPlusServer() throws Exception {
}
}
+ private void performAuthenticationTest(String username, String password, String clientUsername, String clientPassword) throws Exception {
+ final SaslServer saslServer =
+ new SaslServerBuilder(ScramSaslServerFactory.class, SaslMechanismInformation.Names.SCRAM_SHA_1)
+ .setUserName(username)
+ .setPassword(password.toCharArray())
+ .build();
+ CallbackHandler clientHandler = createClientCallbackHandler(clientUsername, clientPassword.toCharArray());
+
+ testAuthentication(SaslMechanismInformation.Names.SCRAM_SHA_1, saslServer, clientHandler, username, EMPTY);
+ }
+
private void testAuthentication(String mechanism, SaslServer saslServer, CallbackHandler clientHandler, String authorizationId, Map clientProps) throws Exception {
final SaslClientFactory clientFactory = obtainSaslClientFactory();
assertNotNull(clientFactory);
diff --git a/tests/base/src/test/java/org/wildfly/security/sasl/scram/ScramServerCompatibilityTest.java b/tests/base/src/test/java/org/wildfly/security/sasl/scram/ScramServerCompatibilityTest.java
index 7c8c7cbcb1b..fbbd40c747c 100644
--- a/tests/base/src/test/java/org/wildfly/security/sasl/scram/ScramServerCompatibilityTest.java
+++ b/tests/base/src/test/java/org/wildfly/security/sasl/scram/ScramServerCompatibilityTest.java
@@ -215,7 +215,7 @@ public void testAllowedAuthorizationId() throws Exception {
assertEquals("v=xzTfS758LckdRoQKN/ZFY/Bauxo=", new String(message, StandardCharsets.UTF_8));
assertTrue(saslServer.isComplete());
- assertEquals(saslServer.getAuthorizationID(), "user");
+ assertEquals("user", saslServer.getAuthorizationID());
}
/**
diff --git a/tests/base/src/test/java/org/wildfly/security/sasl/test/SaslAuthenticationTimeoutTest.java b/tests/base/src/test/java/org/wildfly/security/sasl/test/SaslAuthenticationTimeoutTest.java
index cc6dfc01146..aff240baf5c 100644
--- a/tests/base/src/test/java/org/wildfly/security/sasl/test/SaslAuthenticationTimeoutTest.java
+++ b/tests/base/src/test/java/org/wildfly/security/sasl/test/SaslAuthenticationTimeoutTest.java
@@ -19,6 +19,7 @@
package org.wildfly.security.sasl.test;
import static java.security.AccessController.doPrivileged;
+import static org.wildfly.security.sasl.WildFlySasl.AUTHENTICATION_TIMEOUT;
import java.net.URI;
import java.net.URISyntaxException;
@@ -26,7 +27,7 @@
import java.security.Provider;
import java.security.Security;
import java.util.Collections;
-import java.util.Map;
+import java.util.HashMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
@@ -53,12 +54,8 @@
import org.wildfly.security.sasl.SaslMechanismSelector;
import org.wildfly.security.sasl.digest.DigestServerFactory;
import org.wildfly.security.sasl.digest.WildFlyElytronSaslDigestProvider;
-import org.wildfly.security.sasl.util.AuthenticationTimeoutSaslServerFactory;
import org.wildfly.security.sasl.util.SaslMechanismInformation;
-import mockit.Mock;
-import mockit.MockUp;
-
/**
* Tests a successful authentication timeout for a custom executor service and the default executor service.
*
@@ -75,28 +72,8 @@ public class SaslAuthenticationTimeoutTest {
WildFlyElytronPasswordProvider.getInstance()
};
- /*
- * Unable to set custom AUTHENTICATION_TIMEOUT using a property SaslServer factory (see ELY-1815), so using mock
- * function to avoid using default timeout of 150 sec
- */
- private static void mockGetTimeout() {
- Class> classToMock;
- try {
- classToMock = Class.forName("org.wildfly.security.sasl.util.AuthenticationTimeoutSaslServerFactory", true, AuthenticationTimeoutSaslServerFactory.class.getClassLoader());
- } catch (ClassNotFoundException e) {
- throw new NoClassDefFoundError(e.getMessage());
- }
- new MockUp(classToMock) {
- @Mock
- private long getTimeout(final Map props) {
- return 3;
- }
- };
- }
-
@BeforeClass
public static void registerPasswordProvider() {
- mockGetTimeout();
for (Provider provider : providers) {
Security.insertProviderAt(provider, 1);
}
@@ -121,12 +98,14 @@ public void testSuccessfulTimeout() throws Exception {
INSTANCE.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
try {
-
+ HashMap properties = new HashMap<>();
+ properties.put(AUTHENTICATION_TIMEOUT, "3");
SaslServer server = new SaslServerBuilder(DigestServerFactory.class, DIGEST)
.setUserName("George")
.setPassword("gpwd".toCharArray())
.setProtocol("TestProtocol")
.setServerName("TestServer")
+ .setProperties(properties)
.setScheduledExecutorService(INSTANCE)
.addMechanismRealm("TestRealm")
.build();
@@ -153,12 +132,14 @@ public void testSuccessfulTimeout() throws Exception {
public void testSuccessfulTimeout_DefaultExecuterService() throws Exception {
try {
-
+ HashMap properties = new HashMap<>();
+ properties.put(AUTHENTICATION_TIMEOUT, "3");
SaslServer server = new SaslServerBuilder(DigestServerFactory.class, DIGEST)
.setUserName("George")
.setPassword("gpwd".toCharArray())
.setProtocol("TestProtocol")
.setServerName("TestServer")
+ .setProperties(properties)
.addMechanismRealm("TestRealm")
.build();
diff --git a/tests/base/src/test/java/org/wildfly/security/sasl/test/SaslServerBuilder.java b/tests/base/src/test/java/org/wildfly/security/sasl/test/SaslServerBuilder.java
index edaef80f352..003b4a94328 100644
--- a/tests/base/src/test/java/org/wildfly/security/sasl/test/SaslServerBuilder.java
+++ b/tests/base/src/test/java/org/wildfly/security/sasl/test/SaslServerBuilder.java
@@ -77,7 +77,6 @@
import org.wildfly.security.sasl.util.ChannelBindingSaslServerFactory;
import org.wildfly.security.sasl.util.CredentialSaslServerFactory;
import org.wildfly.security.sasl.util.KeyManagerCredentialSaslServerFactory;
-import org.wildfly.security.sasl.util.PropertiesSaslServerFactory;
import org.wildfly.security.sasl.util.ProtocolSaslServerFactory;
import org.wildfly.security.sasl.util.SecurityProviderSaslServerFactory;
import org.wildfly.security.sasl.util.ServerNameSaslServerFactory;
@@ -377,11 +376,8 @@ public SaslServer build() throws IOException {
if (factory == null && providerSupplier != null) {
factory = new SecurityProviderSaslServerFactory(providerSupplier);
}
- if (properties != null && properties.size() > 0) {
- if (properties.containsKey(WildFlySasl.REALM_LIST)) {
- factory = new AvailableRealmsSaslServerFactory(factory);
- }
- factory = new PropertiesSaslServerFactory(factory, properties);
+ if (properties != null && properties.size() > 0 && properties.containsKey(WildFlySasl.REALM_LIST)) {
+ factory = new AvailableRealmsSaslServerFactory(factory);
}
if (bindingTypeAndData != null) {
factory = new ChannelBindingSaslServerFactory(factory, bindingTypeAndData.key, bindingTypeAndData.value);
@@ -403,6 +399,9 @@ public SaslServer build() throws IOException {
}
final SaslAuthenticationFactory.Builder builder = SaslAuthenticationFactory.builder();
builder.setFactory(factory);
+ if (properties != null && properties.size() > 0) {
+ builder.setProperties(properties);
+ }
builder.setSecurityDomain(securityDomain);
if (scheduledExecutorService != null) {
builder.setScheduledExecutorService(scheduledExecutorService);
diff --git a/tests/base/src/test/java/org/wildfly/security/ssl/SSLAuthenticationTest.java b/tests/base/src/test/java/org/wildfly/security/ssl/SSLAuthenticationTest.java
index 56ed986bb11..b1005514045 100644
--- a/tests/base/src/test/java/org/wildfly/security/ssl/SSLAuthenticationTest.java
+++ b/tests/base/src/test/java/org/wildfly/security/ssl/SSLAuthenticationTest.java
@@ -19,10 +19,10 @@
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
import static org.wildfly.security.ssl.test.util.CAGenerationTool.SIGNATURE_ALGORTHM;
import static org.wildfly.security.x500.X500.OID_AD_OCSP;
import static org.wildfly.security.x500.X500.OID_KP_OCSP_SIGNING;
@@ -37,10 +37,9 @@
import java.net.ServerSocket;
import java.net.SocketException;
import java.net.URI;
-import java.security.Principal;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
import java.security.AccessController;
+import java.security.KeyStore;
+import java.security.Principal;
import java.security.PrivilegedAction;
import java.security.Security;
import java.security.cert.X509Certificate;
@@ -55,17 +54,12 @@
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
-import javax.net.ssl.KeyManager;
-import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
-import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
-import javax.net.ssl.X509ExtendedKeyManager;
-import javax.net.ssl.X509TrustManager;
import javax.security.auth.x500.X500Principal;
import org.bouncycastle.asn1.x500.X500Name;
@@ -79,21 +73,24 @@
import org.junit.BeforeClass;
import org.junit.Test;
import org.wildfly.common.Assert;
-import org.wildfly.security.auth.server.SecurityIdentity;
-import org.wildfly.security.password.WildFlyElytronPasswordProvider;
import org.wildfly.security.auth.client.AuthenticationContext;
import org.wildfly.security.auth.client.AuthenticationContextConfigurationClient;
import org.wildfly.security.auth.realm.KeyStoreBackedSecurityRealm;
import org.wildfly.security.auth.server.SecurityDomain;
+import org.wildfly.security.auth.server.SecurityIdentity;
import org.wildfly.security.auth.server.SecurityRealm;
+import org.wildfly.security.password.WildFlyElytronPasswordProvider;
import org.wildfly.security.permission.PermissionVerifier;
import org.wildfly.security.ssl.test.util.CAGenerationTool;
import org.wildfly.security.ssl.test.util.CAGenerationTool.Identity;
+import org.wildfly.security.ssl.test.util.CustomIdentity;
+import org.wildfly.security.ssl.test.util.DefinedCAIdentity;
+import org.wildfly.security.ssl.test.util.DefinedIdentity;
import org.wildfly.security.x500.GeneralName;
-import org.wildfly.security.x500.principal.X500AttributePrincipalDecoder;
import org.wildfly.security.x500.cert.AccessDescription;
import org.wildfly.security.x500.cert.AuthorityInformationAccessExtension;
import org.wildfly.security.x500.cert.ExtendedKeyUsageExtension;
+import org.wildfly.security.x500.principal.X500AttributePrincipalDecoder;
/**
* Simple test case to test authentication occurring during the establishment of an {@link SSLSession}.
@@ -107,7 +104,7 @@ public class SSLAuthenticationTest {
private final int TESTING_PORT = 18201;
private static final char[] PASSWORD = "Elytron".toCharArray();
- private static final String JKS_LOCATION = "./target/test-classes/jks";
+ private static final String JKS_LOCATION = "./target/test-classes/pkcs12";
private static final String CA_CRL_LOCATION = "./target/test-classes/ca/crl";
private static final String ICA_CRL_LOCATION = "./target/test-classes/ica/crl";
private static final File WORKING_DIR_CACRL = new File(CA_CRL_LOCATION);
@@ -123,64 +120,20 @@ public class SSLAuthenticationTest {
private static final File LADYBUG_REVOKED_PEM_CRL = new File(WORKING_DIR_CACRL, "ladybug-revoked.pem");
private static TestingOcspServer ocspServer = null;
private static X509Certificate ocspResponderCertificate;
-
- /**
- * Get the key manager backed by the specified key store.
- *
- * @param keystorePath the path to the keystore with X509 private key
- * @return the initialised key manager.
- */
- private static X509ExtendedKeyManager getKeyManager(final String keystorePath) throws Exception {
- KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
- keyManagerFactory.init(createKeyStore(keystorePath), PASSWORD);
-
- for (KeyManager current : keyManagerFactory.getKeyManagers()) {
- if (current instanceof X509ExtendedKeyManager) {
- return (X509ExtendedKeyManager) current;
- }
- }
-
- throw new IllegalStateException("Unable to obtain X509ExtendedKeyManager.");
- }
+ private static KeyStore shortWingedKeyStore;
+ private static CustomIdentity goodIdentity;
+ private static CustomIdentity revokedIdentity;
private static TrustManagerFactory getTrustManagerFactory() throws Exception {
return TrustManagerFactory.getInstance("PKIX");
}
- /**
- * Get the trust manager that trusts all certificates signed by the certificate authority.
- *
- * @return the trust manager that trusts all certificates signed by the certificate authority.
- * @throws KeyStoreException
- */
- private static X509TrustManager getCATrustManager() throws Exception {
- TrustManagerFactory trustManagerFactory = getTrustManagerFactory();
- trustManagerFactory.init(createKeyStore("/jks/ca.truststore"));
-
- for (TrustManager current : trustManagerFactory.getTrustManagers()) {
- if (current instanceof X509TrustManager) {
- return (X509TrustManager) current;
- }
- }
-
- throw new IllegalStateException("Unable to obtain X509TrustManager.");
- }
-
private static KeyStore createKeyStore() throws Exception {
- KeyStore ks = KeyStore.getInstance("JKS");
- ks.load(null,null);
+ KeyStore ks = KeyStore.getInstance("PKCS12");
+ ks.load(null, null);
return ks;
}
- private static KeyStore createKeyStore(final String path) throws Exception {
- KeyStore keyStore = KeyStore.getInstance("jks");
- try (InputStream caTrustStoreFile = SSLAuthenticationTest.class.getResourceAsStream(path)) {
- keyStore.load(caTrustStoreFile, PASSWORD);
- }
-
- return keyStore;
- }
-
private static void createTemporaryKeyStoreFile(KeyStore keyStore, File outputFile, char[] password) throws Exception {
if (!outputFile.exists()) {
outputFile.createNewFile();
@@ -190,12 +143,12 @@ private static void createTemporaryKeyStoreFile(KeyStore keyStore, File outputFi
}
}
- private static SecurityDomain getKeyStoreBackedSecurityDomain(String keyStorePath) throws Exception {
- return getKeyStoreBackedSecurityDomain(keyStorePath, true);
+ private static SecurityDomain getKeyStoreBackedSecurityDomain(KeyStore keyStore) throws Exception {
+ return getKeyStoreBackedSecurityDomain(keyStore, true);
}
- private static SecurityDomain getKeyStoreBackedSecurityDomain(String keyStorePath, boolean decoder) throws Exception {
- SecurityRealm securityRealm = new KeyStoreBackedSecurityRealm(createKeyStore(keyStorePath));
+ private static SecurityDomain getKeyStoreBackedSecurityDomain(KeyStore keyStore, boolean decoder) throws Exception {
+ SecurityRealm securityRealm = new KeyStoreBackedSecurityRealm(keyStore);
SecurityDomain.Builder builder = SecurityDomain.builder()
.addRealm("KeystoreRealm", securityRealm)
@@ -222,34 +175,42 @@ public static void beforeTest() throws Exception {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
// Generates certificate and keystore for OCSP responder
- ocspResponderCertificate = caGenerationTool.createIdentity("ocspResponder",
- new X500Principal("OU=Elytron, O=Elytron, C=UK, ST=Elytron, CN=OcspResponder"),
- "ocsp-responder.keystore", Identity.CA, new ExtendedKeyUsageExtension(false, Collections.singletonList(OID_KP_OCSP_SIGNING)));
+ DefinedCAIdentity caIdentity = caGenerationTool.getDefinedCAIdentity(Identity.CA);
+ DefinedCAIdentity intermediateCAIdentity = caGenerationTool.getDefinedCAIdentity(Identity.INTERMEDIATE);
+ CustomIdentity responderIdentity = caIdentity.createIdentity("ocspResponder",
+ new X500Principal("OU=Elytron, O=Elytron, C=UK, ST=Elytron, CN=OcspResponder"),
+ "ocsp-responder.keystore", new ExtendedKeyUsageExtension(false, Collections.singletonList(OID_KP_OCSP_SIGNING)));
+ ocspResponderCertificate = responderIdentity.getCertificate();
// Generates GOOD certificate referencing the OCSP responder
- X509Certificate ocspCheckedGoodCertificate = caGenerationTool.createIdentity("checked",
- new X500Principal("OU=Elytron, O=Elytron, C=UK, ST=Elytron, CN=ocspCheckedGood"),
- "ocsp-checked-good.keystore", Identity.INTERMEDIATE, new AuthorityInformationAccessExtension(Collections.singletonList(
- new AccessDescription(OID_AD_OCSP, new GeneralName.URIName("http://localhost:" + OCSP_PORT + "/ocsp"))
- )));
+ goodIdentity = intermediateCAIdentity.createIdentity("checked",
+ new X500Principal("OU=Elytron, O=Elytron, C=UK, ST=Elytron, CN=ocspCheckedGood"),
+ "ocsp-checked-good.keystore", new AuthorityInformationAccessExtension(Collections.singletonList(
+ new AccessDescription(OID_AD_OCSP, new GeneralName.URIName("http://localhost:" + OCSP_PORT + "/ocsp"))
+ )));
+ X509Certificate ocspCheckedGoodCertificate = goodIdentity.getCertificate();
// Generates REVOKED certificate referencing the OCSP responder
- X509Certificate ocspCheckedRevokedCertificate = caGenerationTool.createIdentity("checked",
- new X500Principal("OU=Elytron, O=Elytron, C=UK, ST=Elytron, CN=ocspCheckedRevoked"),
- "ocsp-checked-revoked.keystore", Identity.CA, (new AuthorityInformationAccessExtension(Collections.singletonList(
- new AccessDescription(OID_AD_OCSP, new GeneralName.URIName("http://localhost:" + OCSP_PORT + "/ocsp"))
- ))));
+ revokedIdentity = caIdentity.createIdentity("checked",
+ new X500Principal("OU=Elytron, O=Elytron, C=UK, ST=Elytron, CN=ocspCheckedRevoked"),
+ "ocsp-checked-revoked.keystore", (new AuthorityInformationAccessExtension(Collections.singletonList(
+ new AccessDescription(OID_AD_OCSP, new GeneralName.URIName("http://localhost:" + OCSP_PORT + "/ocsp"))
+ ))));
+ X509Certificate ocspCheckedRevokedCertificate = revokedIdentity.getCertificate();
// Generates UNKNOWN certificate referencing the OCSP responder
- X509Certificate ocspCheckedUnknownCertificate = caGenerationTool.createIdentity("checked",
- new X500Principal("OU=Elytron, O=Elytron, C=UK, ST=Elytron, CN=ocspCheckedUnknown"),
- "ocsp-checked-unknown.keystore", Identity.CA, new AuthorityInformationAccessExtension(Collections.singletonList(
- new AccessDescription(OID_AD_OCSP, new GeneralName.URIName("http://localhost:" + OCSP_PORT + "/ocsp"))
- )));
-
- X509Certificate greenJuneCertificate = caGenerationTool.getCertificate(Identity.GREENJUNE);
-
- KeyStore beetlesKeyStore = createKeyStore("/jks/beetles.keystore");
+ CustomIdentity unknownIdentity = caIdentity.createIdentity("checked",
+ new X500Principal("OU=Elytron, O=Elytron, C=UK, ST=Elytron, CN=ocspCheckedUnknown"),
+ "ocsp-checked-unknown.keystore", new AuthorityInformationAccessExtension(Collections.singletonList(
+ new AccessDescription(OID_AD_OCSP, new GeneralName.URIName("http://localhost:" + OCSP_PORT + "/ocsp"))
+ )));
+ X509Certificate ocspCheckedUnknownCertificate = unknownIdentity.getCertificate();
+
+ X509Certificate greenJuneCertificate = caGenerationTool
+ .getDefinedIdentity(Identity.GREENJUNE)
+ .getCertificate();
+
+ KeyStore beetlesKeyStore = caGenerationTool.getBeetlesKeyStore();
beetlesKeyStore.setCertificateEntry("ocspResponder", ocspResponderCertificate);
beetlesKeyStore.setCertificateEntry("ocspCheckedGood", ocspCheckedGoodCertificate);
beetlesKeyStore.setCertificateEntry("ocspCheckedRevoked", ocspCheckedRevokedCertificate);
@@ -258,9 +219,9 @@ public static void beforeTest() throws Exception {
createTemporaryKeyStoreFile(beetlesKeyStore, new File(JKS_LOCATION, "beetles.keystore"), PASSWORD);
// Adds trusted cert for shortwinged
- KeyStore shortwingedKeyStore = createKeyStore();
- shortwingedKeyStore.setCertificateEntry("rove", caGenerationTool.getCertificate(Identity.ROVE));
- createTemporaryKeyStoreFile(shortwingedKeyStore, SHORTWINGED_FILE, PASSWORD);
+ shortWingedKeyStore = createKeyStore();
+ shortWingedKeyStore.setCertificateEntry("rove", caGenerationTool.getDefinedIdentity(Identity.ROVE).getCertificate());
+ //createTemporaryKeyStoreFile(shortwingedKeyStore, SHORTWINGED_FILE, PASSWORD);
// Used for all CRLs
Calendar calendar = Calendar.getInstance();
@@ -273,52 +234,53 @@ public static void beforeTest() throws Exception {
// Creates the CRL for ca/crl/blank.pem
X509v2CRLBuilder caBlankCrlBuilder = new X509v2CRLBuilder(
- convertSunStyleToBCStyle(caGenerationTool.getCertificate(Identity.CA).getSubjectDN()),
+ convertSunStyleToBCStyle(caIdentity.getCertificate().getSubjectDN()),
currentDate
);
X509CRLHolder caBlankCrlHolder = caBlankCrlBuilder.setNextUpdate(nextYear).build(
new JcaContentSignerBuilder(SIGNATURE_ALGORTHM)
.setProvider("BC")
- .build(caGenerationTool.getPrivateKey(Identity.CA))
+ .build(caIdentity.getPrivateKey())
);
// Creates the CRL for ica/crl/blank.pem
X509v2CRLBuilder icaBlankCrlBuilder = new X509v2CRLBuilder(
- convertSunStyleToBCStyle(caGenerationTool.getCertificate(Identity.INTERMEDIATE).getSubjectDN()),
+ convertSunStyleToBCStyle(intermediateCAIdentity.getCertificate().getSubjectDN()),
currentDate
);
X509CRLHolder icaBlankCrlHolder = icaBlankCrlBuilder.setNextUpdate(nextYear).build(
new JcaContentSignerBuilder(SIGNATURE_ALGORTHM)
.setProvider("BC")
- .build(caGenerationTool.getPrivateKey(Identity.INTERMEDIATE))
+ .build(intermediateCAIdentity.getPrivateKey())
);
// Creates the CRL for firefly-revoked.pem
X509v2CRLBuilder fireflyRevokedCrlBuilder = new X509v2CRLBuilder(
- convertSunStyleToBCStyle(caGenerationTool.getCertificate(Identity.CA).getSubjectDN()),
+ convertSunStyleToBCStyle(caIdentity.getCertificate().getSubjectDN()),
currentDate
);
fireflyRevokedCrlBuilder.addCRLEntry(
- caGenerationTool.getCertificate(Identity.FIREFLY).getSerialNumber(),
+ caGenerationTool.getDefinedIdentity(Identity.FIREFLY).getCertificate().getSerialNumber(),
revokeDate,
CRLReason.unspecified
);
X509CRLHolder fireflyRevokedCrlHolder = fireflyRevokedCrlBuilder.setNextUpdate(nextYear).build(
new JcaContentSignerBuilder(SIGNATURE_ALGORTHM)
.setProvider("BC")
- .build(caGenerationTool.getPrivateKey(Identity.CA))
+ .build(caIdentity.getPrivateKey())
);
+ DefinedCAIdentity secondCAIdentity = caGenerationTool.getDefinedCAIdentity(Identity.SECOND_CA);
// Creates the CRL for ladybug-revoked.pem
X509v2CRLBuilder ladybugRevokedCrlBuilder = new X509v2CRLBuilder(
- convertSunStyleToBCStyle(caGenerationTool.getCertificate(Identity.SECOND_CA).getSubjectDN()),
+ convertSunStyleToBCStyle(secondCAIdentity.getCertificate().getSubjectDN()),
currentDate
);
// revokes the certificate with serial number #2
ladybugRevokedCrlBuilder.addCRLEntry(
- caGenerationTool.getCertificate(Identity.LADYBUG).getSerialNumber(),
+ caGenerationTool.getDefinedIdentity(Identity.LADYBUG).getCertificate().getSerialNumber(),
revokeDate,
CRLReason.unspecified
);
@@ -326,35 +288,35 @@ public static void beforeTest() throws Exception {
X509CRLHolder ladybugRevokedCrlHolder = ladybugRevokedCrlBuilder.setNextUpdate(nextYear).build(
new JcaContentSignerBuilder(SIGNATURE_ALGORTHM)
.setProvider("BC")
- .build(caGenerationTool.getPrivateKey(Identity.SECOND_CA))
+ .build(secondCAIdentity.getPrivateKey())
);
// Creates the CRL for ica-revoked.pem
X509v2CRLBuilder icaRevokedCrlBuilder = new X509v2CRLBuilder(
- convertSunStyleToBCStyle(caGenerationTool.getCertificate(Identity.CA).getSubjectDN()),
+ convertSunStyleToBCStyle(caIdentity.getCertificate().getSubjectDN()),
currentDate
);
icaRevokedCrlBuilder.addCRLEntry(
- caGenerationTool.getCertificate(Identity.INTERMEDIATE).getSerialNumber(),
+ intermediateCAIdentity.getCertificate().getSerialNumber(),
revokeDate,
CRLReason.unspecified
);
X509CRLHolder icaRevokedCrlHolder = icaRevokedCrlBuilder.setNextUpdate(nextYear).build(
new JcaContentSignerBuilder(SIGNATURE_ALGORTHM)
.setProvider("BC")
- .build(caGenerationTool.getPrivateKey(Identity.CA))
+ .build(caIdentity.getPrivateKey())
);
// Creates the CRL for rove-revoked.pem
X509v2CRLBuilder roveRevokedCrlBuilder = new X509v2CRLBuilder(
- convertSunStyleToBCStyle(caGenerationTool.getCertificate(Identity.INTERMEDIATE).getSubjectDN()),
+ convertSunStyleToBCStyle(intermediateCAIdentity.getCertificate().getSubjectDN()),
currentDate
);
X509CRLHolder roveRevokedCrlHolder = roveRevokedCrlBuilder.setNextUpdate(nextYear).build(
new JcaContentSignerBuilder(SIGNATURE_ALGORTHM)
.setProvider("BC")
- .build(caGenerationTool.getPrivateKey(Identity.INTERMEDIATE))
+ .build(intermediateCAIdentity.getPrivateKey())
);
PemWriter caBlankCrlOutput = new PemWriter(new OutputStreamWriter(new FileOutputStream(CA_BLANK_PEM_CRL)));
@@ -385,9 +347,9 @@ public static void beforeTest() throws Exception {
roveRevokedCrlOutput.close();
ocspServer = new TestingOcspServer(OCSP_PORT);
- ocspServer.createIssuer(1, caGenerationTool.getCertificate(Identity.CA));
- ocspServer.createIssuer(2, caGenerationTool.getCertificate(Identity.INTERMEDIATE));
- ocspServer.createCertificate(1, 1, caGenerationTool.getCertificate(Identity.INTERMEDIATE));
+ ocspServer.createIssuer(1, caIdentity.getCertificate());
+ ocspServer.createIssuer(2, intermediateCAIdentity.getCertificate());
+ ocspServer.createCertificate(1, 1, intermediateCAIdentity.getCertificate());
ocspServer.createCertificate(2, 2, ocspCheckedGoodCertificate);
ocspServer.createCertificate(3, 1, ocspCheckedRevokedCertificate);
ocspServer.revokeCertificate(3, 4);
@@ -433,8 +395,9 @@ public static void afterTest() throws Exception {
@Test
public void testOneWay() throws Throwable {
+ DefinedIdentity firefly = caGenerationTool.getDefinedIdentity(Identity.FIREFLY);
SSLContext serverContext = new SSLContextBuilder()
- .setKeyManager(getKeyManager("/jks/firefly.keystore"))
+ .setKeyManager(firefly.createKeyManager())
.build().create();
performConnectionTest(serverContext, "protocol://test-one-way.org", true, "OU=Elytron,O=Elytron,C=UK,ST=Elytron,CN=Firefly", null, true);
@@ -442,8 +405,9 @@ public void testOneWay() throws Throwable {
@Test
public void testCrlBlank() throws Throwable {
+ DefinedIdentity firefly = caGenerationTool.getDefinedIdentity(Identity.FIREFLY);
SSLContext serverContext = new SSLContextBuilder()
- .setKeyManager(getKeyManager("/jks/firefly.keystore"))
+ .setKeyManager(firefly.createKeyManager())
.build().create();
performConnectionTest(serverContext, "protocol://test-one-way-crl.org", true, "OU=Elytron,O=Elytron,C=UK,ST=Elytron,CN=Firefly", null, true);
@@ -451,8 +415,9 @@ public void testCrlBlank() throws Throwable {
@Test
public void testServerRevoked() throws Throwable {
+ DefinedIdentity firefly = caGenerationTool.getDefinedIdentity(Identity.FIREFLY);
SSLContext serverContext = new SSLContextBuilder()
- .setKeyManager(getKeyManager("/jks/firefly.keystore"))
+ .setKeyManager(firefly.createKeyManager())
.build().create();
performConnectionTest(serverContext, "protocol://test-one-way-firefly-revoked.org", false, null, null, true);
@@ -460,8 +425,9 @@ public void testServerRevoked() throws Throwable {
@Test
public void testServerIcaRevoked() throws Throwable {
+ DefinedIdentity rove = caGenerationTool.getDefinedIdentity(Identity.ROVE);
SSLContext serverContext = new SSLContextBuilder()
- .setKeyManager(getKeyManager("/jks/rove.keystore"))
+ .setKeyManager(rove.createKeyManager())
.build().create();
performConnectionTest(serverContext, "protocol://test-one-way-ica-revoked.org", false, null, null, true);
@@ -474,8 +440,9 @@ public void testServerIcaRevoked() throws Throwable {
*/
@Test
public void testOneWayServerRejectedWithSingleCRL() throws Throwable {
+ DefinedIdentity firefly = caGenerationTool.getDefinedIdentity(Identity.FIREFLY);
SSLContext serverContext = new SSLContextBuilder()
- .setKeyManager(getKeyManager("/jks/firefly.keystore"))
+ .setKeyManager(firefly.createKeyManager())
.build().create();
performConnectionTest(serverContext, "protocol://test-one-way-one-crl.org", false, null, null, true);
@@ -488,8 +455,9 @@ public void testOneWayServerRejectedWithSingleCRL() throws Throwable {
*/
@Test
public void testOneWayServerRejectedWithMultipleCRL() throws Throwable {
+ DefinedIdentity firefly = caGenerationTool.getDefinedIdentity(Identity.FIREFLY);
SSLContext serverContext = new SSLContextBuilder()
- .setKeyManager(getKeyManager("/jks/firefly.keystore"))
+ .setKeyManager(firefly.createKeyManager())
.build().create();
performConnectionTest(serverContext, "protocol://test-one-way-multiple-crls-failure.org", false,
@@ -503,8 +471,9 @@ public void testOneWayServerRejectedWithMultipleCRL() throws Throwable {
*/
@Test
public void testOneWayServerAcceptedWithMultipleCRL() throws Throwable {
+ DefinedIdentity greenJune = caGenerationTool.getDefinedIdentity(Identity.GREENJUNE);
SSLContext serverContext = new SSLContextBuilder()
- .setKeyManager(getKeyManager("/jks/greenjune.keystore"))
+ .setKeyManager(greenJune.createKeyManager())
.build().create();
performConnectionTest(serverContext, "protocol://test-one-way-multiple-crls-success.org", true,
@@ -517,8 +486,9 @@ public void testOneWayServerAcceptedWithMultipleCRL() throws Throwable {
*/
@Test
public void testCRLMaxCertPathSucceeds() throws Throwable {
+ DefinedIdentity rove = caGenerationTool.getDefinedIdentity(Identity.ROVE);
SSLContext serverContext = new SSLContextBuilder()
- .setKeyManager(getKeyManager("/jks/rove.keystore"))
+ .setKeyManager(rove.createKeyManager())
.build().create();
performConnectionTest(serverContext, "protocol://test-one-way-max-cert-path.org", true, "OU=Elytron,O=Elytron,C=UK,ST=Elytron,CN=Rove", null, true);
@@ -531,8 +501,9 @@ public void testCRLMaxCertPathSucceeds() throws Throwable {
*/
@Test
public void testCRLMaxCertPathFails() throws Throwable {
+ DefinedIdentity rove = caGenerationTool.getDefinedIdentity(Identity.ROVE);
SSLContext serverContext = new SSLContextBuilder()
- .setKeyManager(getKeyManager("/jks/rove.keystore"))
+ .setKeyManager(rove.createKeyManager())
.build().create();
performConnectionTest(serverContext, "protocol://test-one-way-max-cert-path-failure.org", false, null, null, true);
@@ -540,10 +511,12 @@ public void testCRLMaxCertPathFails() throws Throwable {
@Test
public void testTwoWay() throws Throwable {
+ DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA);
+ DefinedIdentity scarab = caGenerationTool.getDefinedIdentity(Identity.SCARAB);
SSLContext serverContext = new SSLContextBuilder()
- .setSecurityDomain(getKeyStoreBackedSecurityDomain("/jks/beetles.keystore"))
- .setKeyManager(getKeyManager("/jks/scarab.keystore"))
- .setTrustManager(getCATrustManager())
+ .setSecurityDomain(getKeyStoreBackedSecurityDomain(caGenerationTool.getBeetlesKeyStore()))
+ .setKeyManager(scarab.createKeyManager())
+ .setTrustManager(ca.createTrustManager())
.setNeedClientAuth(true)
.build().create();
@@ -553,10 +526,12 @@ public void testTwoWay() throws Throwable {
@Test
public void testTwoWayNoDecoder() throws Throwable {
+ DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA);
+ DefinedIdentity scarab = caGenerationTool.getDefinedIdentity(Identity.SCARAB);
SSLContext serverContext = new SSLContextBuilder()
- .setSecurityDomain(getKeyStoreBackedSecurityDomain("/jks/beetles.keystore", false))
- .setKeyManager(getKeyManager("/jks/scarab.keystore"))
- .setTrustManager(getCATrustManager())
+ .setSecurityDomain(getKeyStoreBackedSecurityDomain(caGenerationTool.getBeetlesKeyStore(), false))
+ .setKeyManager(scarab.createKeyManager())
+ .setTrustManager(ca.createTrustManager())
.setNeedClientAuth(true)
.build().create();
@@ -566,10 +541,12 @@ public void testTwoWayNoDecoder() throws Throwable {
@Test
public void testTwoWayIca() throws Throwable {
+ DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA);
+ DefinedIdentity scarab = caGenerationTool.getDefinedIdentity(Identity.SCARAB);
SSLContext serverContext = new SSLContextBuilder()
- .setSecurityDomain(getKeyStoreBackedSecurityDomain("/jks/shortwinged.keystore"))
- .setKeyManager(getKeyManager("/jks/scarab.keystore"))
- .setTrustManager(getCATrustManager())
+ .setSecurityDomain(getKeyStoreBackedSecurityDomain(shortWingedKeyStore))
+ .setKeyManager(scarab.createKeyManager())
+ .setTrustManager(ca.createTrustManager())
.setNeedClientAuth(true)
.build().create();
@@ -585,9 +562,10 @@ public void testTwoWayIca() throws Throwable {
public void testAcceptedIssuersConfiguredWithCRL() throws Throwable {
InputStream crl = new FileInputStream("./target/test-classes/ica/crl/blank-blank.pem");
+ DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA);
X509RevocationTrustManager trustManager = X509RevocationTrustManager.builder()
.setTrustManagerFactory(getTrustManagerFactory())
- .setTrustStore(createKeyStore("/jks/ca.truststore"))
+ .setTrustStore(ca.loadKeyStore())
.setCrlStream(crl)
.setPreferCrls(true)
.setNoFallback(true)
@@ -608,11 +586,13 @@ public void testTwoWayClientRejectedWithSingleCRL() throws Throwable {
// this CRL contains the certificate with the alias "ladybug" which is being sent by the client
crlStreams.add(new FileInputStream("target/test-classes/ca/crl/ladybug-revoked.pem"));
+ DefinedCAIdentity secondCA = caGenerationTool.getDefinedCAIdentity(Identity.SECOND_CA);
+ DefinedIdentity firefly = caGenerationTool.getDefinedIdentity(Identity.FIREFLY);
SSLContext serverContext = new SSLContextBuilder()
- .setKeyManager(getKeyManager("/jks/firefly.keystore"))
+ .setKeyManager(firefly.createKeyManager())
.setTrustManager(X509RevocationTrustManager.builder()
.setTrustManagerFactory(getTrustManagerFactory())
- .setTrustStore(createKeyStore("/jks/ca.truststore2"))
+ .setTrustStore(secondCA.loadKeyStore())
.setCrlStreams(crlStreams)
.setPreferCrls(true)
.setNoFallback(true)
@@ -635,12 +615,14 @@ public void testTwoWayClientAcceptedWithSingleCRL() throws Throwable {
// CRL contains "ladybug" certificate but client sends "green june" certificate
crlStreams.add(new FileInputStream("target/test-classes/ca/crl/ladybug-revoked.pem"));
+ DefinedCAIdentity secondCA = caGenerationTool.getDefinedCAIdentity(Identity.SECOND_CA);
+ DefinedIdentity firefly = caGenerationTool.getDefinedIdentity(Identity.FIREFLY);
SSLContext serverContext = new SSLContextBuilder()
- .setSecurityDomain(getKeyStoreBackedSecurityDomain("/jks/beetles.keystore"))
- .setKeyManager(getKeyManager("/jks/firefly.keystore"))
+ .setSecurityDomain(getKeyStoreBackedSecurityDomain(caGenerationTool.getBeetlesKeyStore()))
+ .setKeyManager(firefly.createKeyManager())
.setTrustManager(X509RevocationTrustManager.builder()
.setTrustManagerFactory(getTrustManagerFactory())
- .setTrustStore(createKeyStore("/jks/ca.truststore2"))
+ .setTrustStore(secondCA.loadKeyStore())
.setCrlStreams(crlStreams)
.setPreferCrls(true)
.setNoFallback(true)
@@ -666,11 +648,13 @@ public void testTwoWayClientRejectedWithMultipleCRL() throws Throwable {
crlStreams.add(new FileInputStream("target/test-classes/ca/crl/ladybug-revoked.pem"));
crlStreams.add(new FileInputStream("target/test-classes/ca/crl/firefly-revoked.pem"));
+ DefinedCAIdentity secondCA = caGenerationTool.getDefinedCAIdentity(Identity.SECOND_CA);
+ DefinedIdentity firefly = caGenerationTool.getDefinedIdentity(Identity.FIREFLY);
SSLContext serverContext = new SSLContextBuilder()
- .setKeyManager(getKeyManager("/jks/firefly.keystore"))
+ .setKeyManager(firefly.createKeyManager())
.setTrustManager(X509RevocationTrustManager.builder()
.setTrustManagerFactory(getTrustManagerFactory())
- .setTrustStore(createKeyStore("/jks/ca.truststore2"))
+ .setTrustStore(secondCA.loadKeyStore())
.setCrlStreams(crlStreams)
.setPreferCrls(true)
.setNoFallback(true)
@@ -694,12 +678,14 @@ public void testTwoWayClientAcceptedWithMultipleCRL() throws Throwable {
crlStreams.add(new FileInputStream("target/test-classes/ca/crl/ladybug-revoked.pem"));
crlStreams.add(new FileInputStream("target/test-classes/ca/crl/firefly-revoked.pem"));
+ DefinedCAIdentity secondCA = caGenerationTool.getDefinedCAIdentity(Identity.SECOND_CA);
+ DefinedIdentity firefly = caGenerationTool.getDefinedIdentity(Identity.FIREFLY);
SSLContext serverContext = new SSLContextBuilder()
- .setSecurityDomain(getKeyStoreBackedSecurityDomain("/jks/beetles.keystore"))
- .setKeyManager(getKeyManager("/jks/firefly.keystore"))
+ .setSecurityDomain(getKeyStoreBackedSecurityDomain(caGenerationTool.getBeetlesKeyStore()))
+ .setKeyManager(firefly.createKeyManager())
.setTrustManager(X509RevocationTrustManager.builder()
.setTrustManagerFactory(getTrustManagerFactory())
- .setTrustStore(createKeyStore("/jks/ca.truststore2"))
+ .setTrustStore(secondCA.loadKeyStore())
.setCrlStreams(crlStreams)
.setPreferCrls(true)
.setNoFallback(true)
@@ -714,12 +700,14 @@ public void testTwoWayClientAcceptedWithMultipleCRL() throws Throwable {
@Test
public void testOcspGood() throws Throwable {
+ DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA);
+ DefinedIdentity scarab = caGenerationTool.getDefinedIdentity(Identity.SCARAB);
SSLContext serverContext = new SSLContextBuilder()
- .setSecurityDomain(getKeyStoreBackedSecurityDomain("/jks/beetles.keystore"))
- .setKeyManager(getKeyManager("/jks/scarab.keystore"))
+ .setSecurityDomain(getKeyStoreBackedSecurityDomain(caGenerationTool.getBeetlesKeyStore()))
+ .setKeyManager(scarab.createKeyManager())
.setTrustManager(X509RevocationTrustManager.builder()
.setTrustManagerFactory(getTrustManagerFactory())
- .setTrustStore(createKeyStore("/jks/ca.truststore"))
+ .setTrustStore(ca.loadKeyStore())
.setOcspResponderCert(ocspResponderCertificate)
.build())
.setNeedClientAuth(true)
@@ -729,6 +717,44 @@ public void testOcspGood() throws Throwable {
"OU=Elytron,O=Elytron,C=UK,ST=Elytron,CN=ocspCheckedGood", false);
}
+ @Test
+ public void testOcspRevoked() throws Throwable {
+ DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA);
+ DefinedIdentity scarab = caGenerationTool.getDefinedIdentity(Identity.SCARAB);
+ SSLContext serverContext = new SSLContextBuilder()
+ .setSecurityDomain(getKeyStoreBackedSecurityDomain(caGenerationTool.getBeetlesKeyStore()))
+ .setKeyManager(scarab.createKeyManager())
+ .setTrustManager(X509RevocationTrustManager.builder()
+ .setTrustManagerFactory(getTrustManagerFactory())
+ .setTrustStore(ca.loadKeyStore())
+ .setOcspResponderCert(ocspResponderCertificate)
+ .build())
+ .setNeedClientAuth(true)
+ .build().create();
+
+ performConnectionTest(serverContext, "protocol://test-two-way-ocsp-revoked.org", false, "OU=Elytron,O=Elytron,C=UK,ST=Elytron,CN=Scarab",
+ "OU=Elytron,O=Elytron,C=UK,ST=Elytron,CN=ocspCheckedRevoked", false);
+ }
+
+ @Test
+ public void testOcspUnknown() throws Throwable {
+ DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA);
+ DefinedIdentity scarab = caGenerationTool.getDefinedIdentity(Identity.SCARAB);
+ SSLContext serverContext = new SSLContextBuilder()
+ .setSecurityDomain(getKeyStoreBackedSecurityDomain(caGenerationTool.getBeetlesKeyStore()))
+ .setKeyManager(scarab.createKeyManager())
+ .setTrustManager(X509RevocationTrustManager.builder()
+ .setTrustManagerFactory(getTrustManagerFactory())
+ .setTrustStore(ca.loadKeyStore())
+ .setOcspResponderCert(ocspResponderCertificate)
+ .build())
+ .setNeedClientAuth(true)
+ .build().create();
+
+ performConnectionTest(serverContext, "protocol://test-two-way-ocsp-unknown.org", false, "OU=Elytron,O=Elytron,C=UK,ST=Elytron,CN=Scarab",
+ "OU=Elytron,O=Elytron,C=UK,ST=Elytron,CN=ocspCheckedUnknown", false);
+ }
+
@Test
public void testOcspMaxCertPathNeg1() throws Throwable {
ocspMaxCertPathCommon(-1, false);
@@ -750,12 +776,14 @@ public void testOcspMaxCertPathOkay() throws Throwable {
}
private void ocspMaxCertPathCommon(int maxCertPath, boolean expectValid) throws Throwable {
+ DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA);
+ DefinedIdentity scarab = caGenerationTool.getDefinedIdentity(Identity.SCARAB);
SSLContext serverContext = new SSLContextBuilder()
- .setSecurityDomain(getKeyStoreBackedSecurityDomain("/jks/beetles.keystore"))
- .setKeyManager(getKeyManager("/jks/scarab.keystore"))
+ .setSecurityDomain(getKeyStoreBackedSecurityDomain(caGenerationTool.getBeetlesKeyStore()))
+ .setKeyManager(scarab.createKeyManager())
.setTrustManager(X509RevocationTrustManager.builder()
.setTrustManagerFactory(getTrustManagerFactory())
- .setTrustStore(createKeyStore("/jks/ca.truststore"))
+ .setTrustStore(ca.loadKeyStore())
.setOcspResponderCert(ocspResponderCertificate)
.setMaxCertPath(maxCertPath)
.build())
@@ -768,17 +796,18 @@ private void ocspMaxCertPathCommon(int maxCertPath, boolean expectValid) throws
@Test
public void testClientSideOcsp() throws Throwable {
SSLContext serverContextGood = new SSLContextBuilder()
- .setKeyManager(getKeyManager("/jks/ocsp-checked-good.keystore"))
+ .setKeyManager(goodIdentity.createKeyManager())
.build().create();
SSLContext serverContextRevoked = new SSLContextBuilder()
- .setKeyManager(getKeyManager("/jks/ocsp-checked-revoked.keystore"))
+ .setKeyManager(revokedIdentity.createKeyManager())
.build().create();
+ DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA);
SSLContext clientContext = new SSLContextBuilder()
.setTrustManager(X509RevocationTrustManager.builder()
.setTrustManagerFactory(getTrustManagerFactory())
- .setTrustStore(createKeyStore("/jks/ca.truststore"))
+ .setTrustStore(ca.loadKeyStore())
.setOcspResponderCert(ocspResponderCertificate)
.build())
.setClientMode(true)
@@ -794,8 +823,38 @@ public void testClientSideOcsp() throws Throwable {
}
}
+ @Test
+ public void testWantClientAuthWithCorrectCertificate() throws Throwable {
+ DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA);
+ DefinedIdentity scarab = caGenerationTool.getDefinedIdentity(Identity.SCARAB);
+ SSLContext serverContext = new SSLContextBuilder()
+ .setSecurityDomain(getKeyStoreBackedSecurityDomain(caGenerationTool.getBeetlesKeyStore()))
+ .setKeyManager(scarab.createKeyManager())
+ .setTrustManager(ca.createTrustManager())
+ .setWantClientAuth(true)
+ .build().create();
+
+ performConnectionTest(serverContext, "protocol://test-two-way.org", true, "OU=Elytron,O=Elytron,C=UK,ST=Elytron,CN=Scarab",
+ "OU=Elytron,O=Elytron,C=UK,ST=Elytron,CN=Ladybird", false);
+ }
+
+ @Test
+ public void testWantClientAuthWithIncorrectCertificate() throws Throwable {
+ DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA);
+ DefinedIdentity scarab = caGenerationTool.getDefinedIdentity(Identity.SCARAB);
+ SSLContext serverContext = new SSLContextBuilder()
+ .setSecurityDomain(getKeyStoreBackedSecurityDomain(caGenerationTool.getBeetlesKeyStore()))
+ .setKeyManager(scarab.createKeyManager())
+ .setTrustManager(ca.createTrustManager())
+ .setWantClientAuth(true)
+ .build().create();
+
+ performConnectionTest(serverContext, "protocol://test-one-way.org", true, "OU=Elytron,O=Elytron,C=UK,ST=Elytron,CN=Scarab",
+ null, true);
+ }
+
private void performConnectionTest(SSLContext serverContext, String clientUri, boolean expectValid, String expectedServerPrincipal, String expectedClientPrincipal, boolean oneWay) throws Throwable {
- System.setProperty("wildfly.config.url", SSLAuthenticationTest.class.getResource("wildfly-ssl-test-config-v1_7.xml").toExternalForm());
+ System.setProperty("wildfly.config.url", SSLAuthenticationTest.class.getResource("ssl-authentication-config.xml").toExternalForm());
AccessController.doPrivileged((PrivilegedAction) () -> Security.insertProviderAt(WildFlyElytronPasswordProvider.getInstance(), 1));
AuthenticationContext context = AuthenticationContext.getContextManager().get();
@@ -863,10 +922,10 @@ private void testCommunication(SSLContext serverContext, SSLContext clientContex
}
if (oneWay) {
- assertFalse(clientSocket.getSession().getProtocol().equals("TLSv1.3")); // since TLS 1.3 is not enabled by default (ELY-1917)
+ assertNotEquals("TLSv1.3", clientSocket.getSession().getProtocol());// since TLS 1.3 is not enabled by default (ELY-1917)
} else {
- assertFalse(serverSocket.getSession().getProtocol().equals("TLSv1.3")); // since TLS 1.3 is not enabled by default
- assertFalse(clientSocket.getSession().getProtocol().equals("TLSv1.3")); // since TLS 1.3 is not enabled by default
+ assertNotEquals("TLSv1.3", serverSocket.getSession().getProtocol()); // since TLS 1.3 is not enabled by default
+ assertNotEquals("TLSv1.3", clientSocket.getSession().getProtocol()); // since TLS 1.3 is not enabled by default
}
return received;
} catch (Exception e) {
diff --git a/tests/base/src/test/java/org/wildfly/security/ssl/SSLv2HelloAuthenticationTest.java b/tests/base/src/test/java/org/wildfly/security/ssl/SSLv2HelloAuthenticationTest.java
index 9e61933f29a..392b291de8d 100644
--- a/tests/base/src/test/java/org/wildfly/security/ssl/SSLv2HelloAuthenticationTest.java
+++ b/tests/base/src/test/java/org/wildfly/security/ssl/SSLv2HelloAuthenticationTest.java
@@ -88,8 +88,9 @@
*/
public class SSLv2HelloAuthenticationTest {
+ private static final String CLIENT_CONFIG = "sslv2-hello-authentication-config.xml";
private static final char[] PASSWORD = "Elytron".toCharArray();
- private static final String CA_JKS_LOCATION = "./target/test-classes/ca/jks";
+ private static final String CA_JKS_LOCATION = "./target/test-classes/ca/pkcs12";
private static File ladybirdFile = null;
private static File scarabFile = null;
private static File beetlesFile = null;
@@ -119,7 +120,7 @@ public static void setUp() throws Exception{
createKeyStores(ladybirdFile, scarabFile, beetlesFile, trustFile);
- securityRealm = new KeyStoreBackedSecurityRealm(loadKeyStore("/ca/jks/beetles.keystore"));
+ securityRealm = new KeyStoreBackedSecurityRealm(loadKeyStore("/ca/pkcs12/beetles.keystore"));
securityDomain = SecurityDomain.builder()
.addRealm("KeystoreRealm", securityRealm)
@@ -162,7 +163,7 @@ public void testOneWaySSLv2HelloProtocolMatch() throws Exception {
SSLContext serverContext = new SSLContextBuilder()
.setSecurityDomain(securityDomain)
- .setKeyManager(getKeyManager("/ca/jks/scarab.keystore"))
+ .setKeyManager(getKeyManager("/ca/pkcs12/scarab.keystore"))
.setProtocolSelector(ProtocolSelector.empty().add(EnumSet.copyOf(list)))
.build().create();
@@ -170,7 +171,7 @@ public void testOneWaySSLv2HelloProtocolMatch() throws Exception {
SecurityIdentity identity = performConnectionTest(serverContext,
"protocol://one-way-sslv2hello.org",
- "wildfly-ssl-test-config-v1_6.xml",
+ CLIENT_CONFIG,
enabledProtocols, // We expect client and server socket to only have SSLv2Hello and TLSv1 enabled
"TLSv1"); // We expect the negotiated protocol to be TLSv1, as SSLv2Hello is a pseudo-protocol
}
@@ -187,7 +188,7 @@ public void testTwoWaySSLv2HelloProtocolMatch() throws Exception {
SSLContext serverContext = new SSLContextBuilder()
.setSecurityDomain(securityDomain)
- .setKeyManager(getKeyManager("/ca/jks/scarab.keystore"))
+ .setKeyManager(getKeyManager("/ca/pkcs12/scarab.keystore"))
.setTrustManager(getCATrustManager())
.setNeedClientAuth(true)
.setProtocolSelector(ProtocolSelector.empty().add(EnumSet.copyOf(list)))
@@ -197,7 +198,7 @@ public void testTwoWaySSLv2HelloProtocolMatch() throws Exception {
SecurityIdentity identity = performConnectionTest(serverContext,
"protocol://test-two-way-sslv2hello.org",
- "wildfly-ssl-test-config-v1_6.xml",
+ CLIENT_CONFIG,
enabledProtocols, // We expect client and server socket to only have SSLv2Hello and TLSv1 enabled
"TLSv1"); // We expect the negotiated protocol to be TLSv1, as SSLv2Hello is a pseudo-protocol
@@ -214,7 +215,7 @@ public void testTwoWaySSLv2HelloProtocolMatch() throws Exception {
public void testTwoWaySSLv2HelloNotEnabled() throws Exception {
SSLContext serverContext = new SSLContextBuilder()
.setSecurityDomain(securityDomain)
- .setKeyManager(getKeyManager("/ca/jks/scarab.keystore"))
+ .setKeyManager(getKeyManager("/ca/pkcs12/scarab.keystore"))
.setTrustManager(getCATrustManager())
.setNeedClientAuth(true)
.build().create();
@@ -223,7 +224,7 @@ public void testTwoWaySSLv2HelloNotEnabled() throws Exception {
SecurityIdentity identity = performConnectionTest(serverContext,
"protocol://two-way-no-sslv2hello.org",
- "wildfly-ssl-test-config-v1_6.xml",
+ CLIENT_CONFIG,
enabledProtocols, // We expect the default protocols to be enabled i.e. SSLv2Hello should only be enabled if explicitly configured
"TLSv1.2"); // We expect the negotiated protocol to be the highest version protocol in common
@@ -243,7 +244,7 @@ public void testTwoWaySSLv2HelloNoClientSupport() throws Exception {
SSLContext serverContext = new SSLContextBuilder()
.setSecurityDomain(securityDomain)
- .setKeyManager(getKeyManager("/ca/jks/scarab.keystore"))
+ .setKeyManager(getKeyManager("/ca/pkcs12/scarab.keystore"))
.setTrustManager(getCATrustManager())
.setNeedClientAuth(true)
.setProtocolSelector(ProtocolSelector.empty().add(EnumSet.copyOf(list)))
@@ -254,7 +255,7 @@ public void testTwoWaySSLv2HelloNoClientSupport() throws Exception {
SecurityIdentity identity = performConnectionTest(serverContext,
"protocol://two-way-no-sslv2hello.org",
- "wildfly-ssl-test-config-v1_6.xml",
+ CLIENT_CONFIG,
enabledClientProtocols,
enabledServerProtocols,
"TLSv1"); // We expect the negotiated protocol to be the highest version protocol in common
@@ -273,7 +274,7 @@ public void testTwoWaySSlv2HelloNoServerSupport() throws Exception {
SSLContext serverContext = new SSLContextBuilder()
.setSecurityDomain(securityDomain)
- .setKeyManager(getKeyManager("/ca/jks/scarab.keystore"))
+ .setKeyManager(getKeyManager("/ca/pkcs12/scarab.keystore"))
.setTrustManager(getCATrustManager())
.setNeedClientAuth(true)
.setProtocolSelector(ProtocolSelector.empty().add(EnumSet.copyOf(list)))
@@ -284,7 +285,7 @@ public void testTwoWaySSlv2HelloNoServerSupport() throws Exception {
SecurityIdentity identity = performConnectionTest(serverContext,
"protocol://test-two-way-sslv2hello.org",
- "wildfly-ssl-test-config-v1_6.xml",
+ CLIENT_CONFIG,
clientEnabledProtocols,
serverEnabledProtocols,
"NONE"); // handshake is expected to fail, which in turn returns an empty SSLSession
@@ -376,7 +377,7 @@ private static X509ExtendedKeyManager getKeyManager(final String keystorePath) t
*/
private static X509TrustManager getCATrustManager() throws Exception {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
- trustManagerFactory.init(loadKeyStore("/ca/jks/ca.truststore"));
+ trustManagerFactory.init(loadKeyStore("/ca/pkcs12/ca.truststore"));
for (TrustManager current : trustManagerFactory.getTrustManagers()) {
if (current instanceof X509TrustManager) {
@@ -388,13 +389,13 @@ private static X509TrustManager getCATrustManager() throws Exception {
}
private static KeyStore loadKeyStore() throws Exception{
- KeyStore ks = KeyStore.getInstance("JKS");
+ KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(null,null);
return ks;
}
private static KeyStore loadKeyStore(final String path) throws Exception {
- KeyStore keyStore = KeyStore.getInstance("jks");
+ KeyStore keyStore = KeyStore.getInstance("PKCS12");
try (InputStream caTrustStoreFile = SSLAuthenticationTest.class.getResourceAsStream(path)) {
keyStore.load(caTrustStoreFile, PASSWORD);
}
diff --git a/tests/base/src/test/java/org/wildfly/security/ssl/TLS13AuthenticationTest.java b/tests/base/src/test/java/org/wildfly/security/ssl/TLS13AuthenticationTest.java
index 422fbf89712..826916e29ca 100644
--- a/tests/base/src/test/java/org/wildfly/security/ssl/TLS13AuthenticationTest.java
+++ b/tests/base/src/test/java/org/wildfly/security/ssl/TLS13AuthenticationTest.java
@@ -23,12 +23,9 @@
import java.io.Closeable;
import java.io.IOException;
-import java.io.InputStream;
import java.net.InetAddress;
import java.net.URI;
import java.security.AccessController;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
import java.security.PrivilegedAction;
import java.security.Security;
import java.util.Locale;
@@ -36,20 +33,13 @@
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
-import javax.net.ssl.KeyManager;
-import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.TrustManagerFactory;
-import javax.net.ssl.X509ExtendedKeyManager;
-import javax.net.ssl.X509TrustManager;
import org.junit.AfterClass;
-import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.Test;
import org.wildfly.security.WildFlyElytronProvider;
@@ -62,6 +52,8 @@
import org.wildfly.security.permission.PermissionVerifier;
import org.wildfly.security.ssl.test.util.CAGenerationTool;
import org.wildfly.security.ssl.test.util.CAGenerationTool.Identity;
+import org.wildfly.security.ssl.test.util.DefinedCAIdentity;
+import org.wildfly.security.ssl.test.util.DefinedIdentity;
import org.wildfly.security.x500.principal.X500AttributePrincipalDecoder;
/**
@@ -71,23 +63,22 @@
*/
public class TLS13AuthenticationTest {
+ private static final String CLIENT_CONFIG = "tls13-authentication-config.xml";
private static final char[] PASSWORD = "Elytron".toCharArray();
- private static final String CA_JKS_LOCATION = "./target/test-classes/jks";
+ private static final String CA_JKS_LOCATION = "./target/test-classes/pkcs12";
private static CAGenerationTool caGenerationTool = null;
private static SecurityDomain securityDomain = null;
@BeforeClass
public static void setUp() throws Exception{
- Assume.assumeTrue("Skipping TLS13AuthenticationTest suite, tests are not being run on JDK 11.",
- System.getProperty("java.specification.version").equals("11"));
caGenerationTool = CAGenerationTool.builder()
.setBaseDir(CA_JKS_LOCATION)
.setRequestIdentities(Identity.LADYBIRD, Identity.SCARAB)
.build();
- SecurityRealm securityRealm = new KeyStoreBackedSecurityRealm(loadKeyStore("/jks/beetles.keystore"));
+ SecurityRealm securityRealm = new KeyStoreBackedSecurityRealm(caGenerationTool.getBeetlesKeyStore());
securityDomain = SecurityDomain.builder()
.addRealm("KeystoreRealm", securityRealm)
.build()
@@ -108,15 +99,18 @@ public static void cleanUp() throws IOException {
public void testTwoWayTLS13() throws Exception {
final String CIPHER_SUITE = "TLS_AES_128_GCM_SHA256";
+ DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA);
+ DefinedIdentity scarab = caGenerationTool.getDefinedIdentity(Identity.SCARAB);
+
SSLContext serverContext = new SSLContextBuilder()
.setSecurityDomain(securityDomain)
.setCipherSuiteSelector(CipherSuiteSelector.fromNamesString(CIPHER_SUITE))
- .setKeyManager(getKeyManager("/jks/scarab.keystore"))
- .setTrustManager(getCATrustManager())
+ .setKeyManager(scarab.createKeyManager())
+ .setTrustManager(ca.createTrustManager())
.setNeedClientAuth(true)
.build().create();
- SecurityIdentity identity = performConnectionTest(serverContext, "protocol://test-two-way-tls13.org", "wildfly-ssl-test-config-v1_5.xml", CIPHER_SUITE, true);
+ SecurityIdentity identity = performConnectionTest(serverContext, "protocol://test-two-way-tls13.org", CLIENT_CONFIG, CIPHER_SUITE, true);
assertNotNull(identity);
assertEquals("Principal Name", "ladybird", identity.getPrincipal().getName());
}
@@ -127,15 +121,18 @@ public void testDifferentPreferredTLS13Suites() throws Exception {
final String PREFERRED_CIPHER_SUITE = "TLS_AES_256_GCM_SHA384";
final String SERVER_CIPHER_SUITE = String.format("%s:%s", PREFERRED_CIPHER_SUITE, REQUIRED_CIPHER_SUITE);
+ DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA);
+ DefinedIdentity scarab = caGenerationTool.getDefinedIdentity(Identity.SCARAB);
+
SSLContext serverContext = new SSLContextBuilder()
.setSecurityDomain(securityDomain)
.setCipherSuiteSelector(CipherSuiteSelector.fromNamesString(SERVER_CIPHER_SUITE))
- .setKeyManager(getKeyManager("/jks/scarab.keystore"))
- .setTrustManager(getCATrustManager())
+ .setKeyManager(scarab.createKeyManager())
+ .setTrustManager(ca.createTrustManager())
.setNeedClientAuth(true)
.build().create();
- SecurityIdentity identity = performConnectionTest(serverContext, "protocol://test-different-preferred-tls13-suites.org", "wildfly-ssl-test-config-v1_5.xml", REQUIRED_CIPHER_SUITE, true);
+ SecurityIdentity identity = performConnectionTest(serverContext, "protocol://test-different-preferred-tls13-suites.org", CLIENT_CONFIG, REQUIRED_CIPHER_SUITE, true);
assertNotNull(identity);
assertEquals("Principal Name", "ladybird", identity.getPrincipal().getName());
}
@@ -145,18 +142,21 @@ public void testClientTLS12Only() throws Exception {
final String TLS13_CIPHER_SUITE = "TLS_AES_128_GCM_SHA256";
final String TLS12_CIPHER_SUITE = "TLS_RSA_WITH_AES_128_CBC_SHA256"; // TLS v1.2
+ DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA);
+ DefinedIdentity scarab = caGenerationTool.getDefinedIdentity(Identity.SCARAB);
+
SSLContext serverContext = new SSLContextBuilder()
.setSecurityDomain(securityDomain)
.setCipherSuiteSelector(CipherSuiteSelector.aggregate(
CipherSuiteSelector.fromNamesString(TLS13_CIPHER_SUITE),
CipherSuiteSelector.fromString(TLS12_CIPHER_SUITE)
))
- .setKeyManager(getKeyManager("/jks/scarab.keystore"))
- .setTrustManager(getCATrustManager())
+ .setKeyManager(scarab.createKeyManager())
+ .setTrustManager(ca.createTrustManager())
.setNeedClientAuth(true)
.build().create();
- SecurityIdentity identity = performConnectionTest(serverContext, "protocol://test-client-tls12-only.org", "wildfly-ssl-test-config-v1_5.xml", TLS12_CIPHER_SUITE, false);
+ SecurityIdentity identity = performConnectionTest(serverContext, "protocol://test-client-tls12-only.org", CLIENT_CONFIG, TLS12_CIPHER_SUITE, false);
assertNotNull(identity);
assertEquals("Principal Name", "ladybird", identity.getPrincipal().getName());
}
@@ -165,15 +165,18 @@ public void testClientTLS12Only() throws Exception {
public void testServerTLS12Only() throws Exception {
final String SERVER_CIPHER_SUITE = "TLS_RSA_WITH_AES_128_CBC_SHA256"; // TLS v1.2
+ DefinedCAIdentity ca = caGenerationTool.getDefinedCAIdentity(Identity.CA);
+ DefinedIdentity scarab = caGenerationTool.getDefinedIdentity(Identity.SCARAB);
+
SSLContext serverContext = new SSLContextBuilder()
.setSecurityDomain(securityDomain)
.setCipherSuiteSelector(CipherSuiteSelector.fromString(SERVER_CIPHER_SUITE))
- .setKeyManager(getKeyManager("/jks/scarab.keystore"))
- .setTrustManager(getCATrustManager())
+ .setKeyManager(scarab.createKeyManager())
+ .setTrustManager(ca.createTrustManager())
.setNeedClientAuth(true)
.build().create();
- SecurityIdentity identity = performConnectionTest(serverContext, "protocol://test-server-tls12-only.org", "wildfly-ssl-test-config-v1_5.xml", SERVER_CIPHER_SUITE, false);
+ SecurityIdentity identity = performConnectionTest(serverContext, "protocol://test-server-tls12-only.org", CLIENT_CONFIG, SERVER_CIPHER_SUITE, false);
assertNotNull(identity);
assertEquals("Principal Name", "ladybird", identity.getPrincipal().getName());
}
@@ -182,12 +185,14 @@ public void testServerTLS12Only() throws Exception {
public void testOneWayTLS13() throws Exception {
final String CIPHER_SUITE = "TLS_AES_128_GCM_SHA256";
+ DefinedIdentity scarab = caGenerationTool.getDefinedIdentity(Identity.SCARAB);
+
SSLContext serverContext = new SSLContextBuilder()
.setCipherSuiteSelector(CipherSuiteSelector.fromNamesString(CIPHER_SUITE))
- .setKeyManager(getKeyManager("/jks/scarab.keystore"))
+ .setKeyManager(scarab.createKeyManager())
.build().create();
- SecurityIdentity identity = performConnectionTest(serverContext, "protocol://test-one-way-tls13.org", "wildfly-ssl-test-config-v1_5.xml", CIPHER_SUITE, true);
+ SecurityIdentity identity = performConnectionTest(serverContext, "protocol://test-one-way-tls13.org", CLIENT_CONFIG, CIPHER_SUITE, true);
assertNull(identity);
}
@@ -244,53 +249,6 @@ private SecurityIdentity performConnectionTest(SSLContext serverContext, String
}
}
- /**
- * Get the key manager backed by the specified key store.
- *
- * @param keystorePath the path to the keystore with X509 private key
- * @return the initialised key manager.
- */
- private static X509ExtendedKeyManager getKeyManager(final String keystorePath) throws Exception {
- KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
- keyManagerFactory.init(loadKeyStore(keystorePath), PASSWORD);
-
- for (KeyManager current : keyManagerFactory.getKeyManagers()) {
- if (current instanceof X509ExtendedKeyManager) {
- return (X509ExtendedKeyManager) current;
- }
- }
-
- throw new IllegalStateException("Unable to obtain X509ExtendedKeyManager.");
- }
-
- /**
- * Get the trust manager that trusts all certificates signed by the certificate authority.
- *
- * @return the trust manager that trusts all certificates signed by the certificate authority.
- * @throws KeyStoreException
- */
- private static X509TrustManager getCATrustManager() throws Exception {
- TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
- trustManagerFactory.init(loadKeyStore("/jks/ca.truststore"));
-
- for (TrustManager current : trustManagerFactory.getTrustManagers()) {
- if (current instanceof X509TrustManager) {
- return (X509TrustManager) current;
- }
- }
-
- throw new IllegalStateException("Unable to obtain X509TrustManager.");
- }
-
- private static KeyStore loadKeyStore(final String path) throws Exception {
- KeyStore keyStore = KeyStore.getInstance("jks");
- try (InputStream caTrustStoreFile = SSLAuthenticationTest.class.getResourceAsStream(path)) {
- keyStore.load(caTrustStoreFile, PASSWORD);
- }
-
- return keyStore;
- }
-
private void safeClose(Closeable closeable) {
try {
closeable.close();
diff --git a/tests/base/src/test/java/org/wildfly/security/ssl/TestingOcspServer.java b/tests/base/src/test/java/org/wildfly/security/ssl/TestingOcspServer.java
index 81f641e08c5..027df60a885 100644
--- a/tests/base/src/test/java/org/wildfly/security/ssl/TestingOcspServer.java
+++ b/tests/base/src/test/java/org/wildfly/security/ssl/TestingOcspServer.java
@@ -18,6 +18,7 @@
package org.wildfly.security.ssl;
+import static java.nio.charset.StandardCharsets.UTF_8;
import static org.mockserver.model.HttpRequest.request;
import static org.mockserver.model.HttpResponse.response;
@@ -42,6 +43,7 @@
import org.mockserver.integration.ClientAndServer;
import org.mockserver.matchers.Times;
import org.mockserver.model.Header;
+import org.mockserver.model.HttpRequest;
import org.mockserver.model.HttpResponse;
import org.mockserver.model.NottableString;
import org.wildfly.common.iteration.ByteIterator;
@@ -126,32 +128,13 @@ public void start() throws Exception {
.withMethod("POST")
.withPath("/ocsp"),
Times.unlimited())
- .respond(request -> {
- ByteBuf buffer = Unpooled.wrappedBuffer(request.getBody().getRawBytes());
- FullHttpRequest nettyRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_0, HttpMethod.POST, request.getPath().getValue(), buffer);
- for (Header header : request.getHeaderList()) {
- for (NottableString value : header.getValues()) {
- nettyRequest.headers().add(header.getName().getValue(), value.getValue());
- }
- }
-
- FullHttpResponse nettyResponse;
- try {
- nettyResponse = servlet.service(nettyRequest, new ServletURI(request.getPath().getValue()), null, SslReverseProxyMode.NONE);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
-
- HttpResponse response = response()
- .withStatusCode(nettyResponse.status().code())
- .withBody(nettyResponse.content().array());
-
- for (Map.Entry header : nettyResponse.headers()) {
- response.withHeader(header.getKey(), header.getValue());
- }
-
- return response;
- });
+ .respond(request -> getHttpResponse(request, servlet));
+ server.when(
+ request()
+ .withMethod("GET")
+ .withPath("/ocsp/.*"),
+ Times.unlimited())
+ .respond(request -> getHttpResponse(request, servlet));
}
public void stop() throws SQLException {
@@ -198,4 +181,40 @@ public void revokeCertificate(int id, int reason) throws SQLException {
statement.execute();
}
+
+
+ public HttpResponse getHttpResponse(HttpRequest request, HttpOcspServlet servlet){
+ byte[] body;
+ HttpMethod method;
+ if (request.getBody() == null) {
+ method = HttpMethod.GET;
+ body = request.getPath().getValue().split("/ocsp/", 2)[1].getBytes(UTF_8);
+ } else {
+ method = HttpMethod.POST;
+ body = request.getBody().getRawBytes();
+ }
+ ByteBuf buffer = Unpooled.wrappedBuffer(body);
+ FullHttpRequest nettyRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_0, method, request.getPath().getValue(), buffer);
+ for (Header header : request.getHeaderList()) {
+ for (NottableString value : header.getValues()) {
+ nettyRequest.headers().add(header.getName().getValue(), value.getValue());
+ }
+ }
+
+ FullHttpResponse nettyResponse;
+ try {
+ nettyResponse = servlet.service(nettyRequest, new ServletURI(request.getPath().getValue()), null, SslReverseProxyMode.NONE);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ HttpResponse response = response()
+ .withStatusCode(nettyResponse.status().code())
+ .withBody(nettyResponse.content().array());
+
+ for (Map.Entry header : nettyResponse.headers()) {
+ response.withHeader(header.getKey(), header.getValue());
+ }
+ return response;
+ }
}
\ No newline at end of file
diff --git a/tests/base/src/test/java/org/wildfly/security/util/PasswordBasedEncryptionUtilTest.java b/tests/base/src/test/java/org/wildfly/security/util/PasswordBasedEncryptionUtilTest.java
index b0fc5420197..d3e511c4547 100644
--- a/tests/base/src/test/java/org/wildfly/security/util/PasswordBasedEncryptionUtilTest.java
+++ b/tests/base/src/test/java/org/wildfly/security/util/PasswordBasedEncryptionUtilTest.java
@@ -183,9 +183,9 @@ private void checkPb(String secret, String salt, int iteration, String pbGenerat
String crossDecrypted = new String(decryptUtil.decodeAndDecrypt(pbGenerated));
String decrypted = new String(decryptUtil.decodeAndDecrypt(encrypted));
- Assert.assertTrue("Elytron in PB compatible mode failed", decrypted.equals(secret));
- Assert.assertTrue("PicketBox encrypted, Elytron decrypted in compatible mode, failed", crossDecrypted.equals(secret));
- Assert.assertTrue("Elytron in compatible mode encrypted, PicketBox encrypted must be the same", pbGenerated.equals(encrypted));
+ Assert.assertEquals("Elytron in PB compatible mode failed", secret, decrypted);
+ Assert.assertEquals("PicketBox encrypted, Elytron decrypted in compatible mode, failed", secret, crossDecrypted);
+ Assert.assertEquals("Elytron in compatible mode encrypted, PicketBox encrypted must be the same", encrypted, pbGenerated);
}
diff --git a/tests/base/src/test/java/org/wildfly/security/x500/X500AttributePrincipalDecoderTest.java b/tests/base/src/test/java/org/wildfly/security/x500/X500AttributePrincipalDecoderTest.java
index 19a3fa89433..e383455e799 100644
--- a/tests/base/src/test/java/org/wildfly/security/x500/X500AttributePrincipalDecoderTest.java
+++ b/tests/base/src/test/java/org/wildfly/security/x500/X500AttributePrincipalDecoderTest.java
@@ -19,6 +19,7 @@
package org.wildfly.security.x500;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import javax.security.auth.x500.X500Principal;
@@ -61,9 +62,40 @@ public void testDecodeAttributeWithSubrange() {
assertEquals("jboss.redhat.com", decoder.getName(principal));
}
+ @Test
+ public void testDecodeWithAggregation() {
+ X500Principal principal = new X500Principal("cn=bob.smith,cn=bob,ou=people,dc=example,dc=redhat,dc=com");
+ PrincipalDecoder dcDecoder = new X500AttributePrincipalDecoder(X500.OID_DC);
+ PrincipalDecoder cnDecoder = new X500AttributePrincipalDecoder(X500.OID_AT_COMMON_NAME, 1);
+ PrincipalDecoder aggregateDecoder = PrincipalDecoder.aggregate(cnDecoder, dcDecoder);
+ assertEquals("bob.smith", aggregateDecoder.getName(principal));
+ aggregateDecoder = PrincipalDecoder.aggregate(dcDecoder, cnDecoder);
+ assertEquals("example.redhat.com", aggregateDecoder.getName(principal));
+
+ principal = new X500Principal("cn=bob.smith,ou=people,dc=example,dc=redhat");
+ cnDecoder = new X500AttributePrincipalDecoder(X500.OID_AT_COMMON_NAME);
+ PrincipalDecoder ouDecoder = new X500AttributePrincipalDecoder(X500.OID_AT_ORGANIZATIONAL_UNIT_NAME, 1);
+ dcDecoder = new X500AttributePrincipalDecoder(X500.OID_DC, 1);
+ PrincipalDecoder dcDecoder1 = new X500AttributePrincipalDecoder(X500.OID_DC, 1, 1);
+ aggregateDecoder = PrincipalDecoder.aggregate(dcDecoder1, dcDecoder, ouDecoder, cnDecoder);
+ assertEquals("redhat", aggregateDecoder.getName(principal));
+ aggregateDecoder = PrincipalDecoder.aggregate(dcDecoder, dcDecoder1, ouDecoder, cnDecoder);
+ assertEquals("example", aggregateDecoder.getName(principal));
+ aggregateDecoder = PrincipalDecoder.aggregate(cnDecoder, dcDecoder1, dcDecoder, ouDecoder);
+ assertEquals("bob.smith", aggregateDecoder.getName(principal));
+
+ principal = new X500Principal("cn=bob.smith,dc=example,dc=redhat");
+ aggregateDecoder = PrincipalDecoder.aggregate(ouDecoder);
+ assertNull(aggregateDecoder.getName(principal));
+ aggregateDecoder = PrincipalDecoder.aggregate(dcDecoder, ouDecoder);
+ assertEquals("example", aggregateDecoder.getName(principal));
+ aggregateDecoder = PrincipalDecoder.aggregate(dcDecoder, ouDecoder, dcDecoder1);
+ assertEquals("example", aggregateDecoder.getName(principal));
+ }
+
@Test
public void testDecodeWithConcatenation() {
- X500Principal principal; new X500Principal("cn=bob.smith,cn=bob,ou=people,dc=example,dc=redhat,dc=com");
+ X500Principal principal;
PrincipalDecoder dcDecoder, dcDecoder1, cnDecoder, ouDecoder, concatenatingDecoder;
principal = new X500Principal("cn=bob.smith,cn=bob,ou=people,dc=example,dc=redhat,dc=com");
dcDecoder = new X500AttributePrincipalDecoder(X500.OID_DC);
diff --git a/tests/base/src/test/resources/org/wildfly/security/auth/client/wildfly-masked-password-ssl-config-v1_4.xml b/tests/base/src/test/resources/org/wildfly/security/auth/client/wildfly-masked-password-ssl-config-v1_4.xml
index d458d71f173..133acd78f2d 100644
--- a/tests/base/src/test/resources/org/wildfly/security/auth/client/wildfly-masked-password-ssl-config-v1_4.xml
+++ b/tests/base/src/test/resources/org/wildfly/security/auth/client/wildfly-masked-password-ssl-config-v1_4.xml
@@ -21,10 +21,11 @@
-
+
+
-
+
diff --git a/tests/base/src/test/resources/org/wildfly/security/ssl/ocsp-responder.xml b/tests/base/src/test/resources/org/wildfly/security/ssl/ocsp-responder.xml
index 50b99e567d7..8b0d7755811 100644
--- a/tests/base/src/test/resources/org/wildfly/security/ssl/ocsp-responder.xml
+++ b/tests/base/src/test/resources/org/wildfly/security/ssl/ocsp-responder.xml
@@ -19,7 +19,7 @@
JKS
- password=Elytron,keystore=file:target/test-classes/jks/ocsp-responder.keystore
+ password=Elytron,keystore=file:target/test-classes/pkcs12/ocsp-responder.keystore
SHA256withRSA
diff --git a/tests/base/src/test/resources/org/wildfly/security/ssl/wildfly-ssl-test-config-v1_7.xml b/tests/base/src/test/resources/org/wildfly/security/ssl/ssl-authentication-config.xml
similarity index 86%
rename from tests/base/src/test/resources/org/wildfly/security/ssl/wildfly-ssl-test-config-v1_7.xml
rename to tests/base/src/test/resources/org/wildfly/security/ssl/ssl-authentication-config.xml
index a323343d443..2f5ab506492 100644
--- a/tests/base/src/test/resources/org/wildfly/security/ssl/wildfly-ssl-test-config-v1_7.xml
+++ b/tests/base/src/test/resources/org/wildfly/security/ssl/ssl-authentication-config.xml
@@ -17,44 +17,49 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
+
+
-
-
+
+
+
-
-
+
+
+
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
diff --git a/tests/base/src/test/resources/org/wildfly/security/ssl/wildfly-ssl-test-config-v1_6.xml b/tests/base/src/test/resources/org/wildfly/security/ssl/sslv2-hello-authentication-config.xml
similarity index 86%
rename from tests/base/src/test/resources/org/wildfly/security/ssl/wildfly-ssl-test-config-v1_6.xml
rename to tests/base/src/test/resources/org/wildfly/security/ssl/sslv2-hello-authentication-config.xml
index 197b3d44798..c3ca9fc93d3 100644
--- a/tests/base/src/test/resources/org/wildfly/security/ssl/wildfly-ssl-test-config-v1_6.xml
+++ b/tests/base/src/test/resources/org/wildfly/security/ssl/sslv2-hello-authentication-config.xml
@@ -17,14 +17,17 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
+
+
-
-
+
+
+
-
-
+
+
diff --git a/tests/base/src/test/resources/org/wildfly/security/ssl/wildfly-ssl-test-config-v1_5.xml b/tests/base/src/test/resources/org/wildfly/security/ssl/tls13-authentication-config.xml
similarity index 90%
rename from tests/base/src/test/resources/org/wildfly/security/ssl/wildfly-ssl-test-config-v1_5.xml
rename to tests/base/src/test/resources/org/wildfly/security/ssl/tls13-authentication-config.xml
index e23e2fa28ee..e9146013afc 100644
--- a/tests/base/src/test/resources/org/wildfly/security/ssl/wildfly-ssl-test-config-v1_5.xml
+++ b/tests/base/src/test/resources/org/wildfly/security/ssl/tls13-authentication-config.xml
@@ -17,14 +17,17 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
+
+
-
-
+
+
+
-
-
+
+
diff --git a/tests/common/pom.xml b/tests/common/pom.xml
index 12b05419de4..106ac094654 100644
--- a/tests/common/pom.xml
+++ b/tests/common/pom.xml
@@ -5,7 +5,7 @@
wildfly-elytron-parent
org.wildfly.security
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
@@ -74,7 +74,7 @@
test
- org.glassfish
+ org.eclipse.parsson
jakarta.json
test
diff --git a/tests/common/src/test/java/org/wildfly/security/ssl/test/util/CAGenerationTool.java b/tests/common/src/test/java/org/wildfly/security/ssl/test/util/CAGenerationTool.java
index 86526400a81..e28f884d999 100644
--- a/tests/common/src/test/java/org/wildfly/security/ssl/test/util/CAGenerationTool.java
+++ b/tests/common/src/test/java/org/wildfly/security/ssl/test/util/CAGenerationTool.java
@@ -55,7 +55,8 @@
import org.wildfly.security.x500.cert.X509CertificateExtension;
/**
- * A tool for generating a complete set of certificates backed by a generated certificate authority.
+ * A tool for generating a complete set of certificates backed by a generated
+ * certificate authority.
*
* @author Darran Lofthouse
*/
@@ -65,12 +66,13 @@ public class CAGenerationTool implements Closeable {
private static final String BEETLES_STORE = "beetles.keystore";
private static final String KEY_ALGORITHM = "RSA";
- private static final String KEYSTORE_TYPE = "JKS"; // TODO Switch to PKCS#12
+ private static final String KEYSTORE_TYPE = "PKCS12";
private static final int OCSP_PORT = 4854;
- private static final char[] PASSWORD = "Elytron".toCharArray();
+ static final char[] PASSWORD = "Elytron".toCharArray();
private static final Set BEETLES = Collections
- .unmodifiableSet(new HashSet<>(Arrays.asList(Identity.LADYBIRD, Identity.SCARAB, Identity.DUNG, Identity.FIREFLY)));
+ .unmodifiableSet(
+ new HashSet<>(Arrays.asList(Identity.LADYBIRD, Identity.SCARAB, Identity.DUNG, Identity.FIREFLY)));
private static final Predicate INCLUDE_IN_BEETLES = BEETLES::contains;
private final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
@@ -79,6 +81,8 @@ public class CAGenerationTool implements Closeable {
private final File workingDir;
+ private volatile boolean closed = false;
+
protected CAGenerationTool(Builder builder) throws Exception {
// Ensure we have the directory created to hold the resulting KeyStores
workingDir = new File(builder.baseDir);
@@ -108,13 +112,58 @@ protected CAGenerationTool(Builder builder) throws Exception {
}
}
+ public DefinedIdentity getDefinedIdentity(final Identity identity) {
+ if (identity.isCertificateAuthority()) {
+ return getDefinedCAIdentity(identity);
+ }
+
+ if (!certificateMap.containsKey(identity)) {
+ throw new IllegalStateException(String.format("Identity %s has not been created.", identity.toString()));
+ }
+
+ X509Certificate certificate = certificateMap.get(identity);
+
+ return new DefinedIdentity(this, identity, certificate);
+ }
+
+ public DefinedCAIdentity getDefinedCAIdentity(final Identity identity) {
+ if (!identity.isCertificateAuthority()) {
+ throw new IllegalStateException(
+ String.format("Identity %s is not a CertificateAuthority", identity.toString()));
+ }
+
+ if (!caMap.containsKey(identity)) {
+ throw new IllegalStateException(String.format("Identity %s has not been created.", identity.toString()));
+ }
+
+ CAState caState = caMap.get(identity);
+ return new DefinedCAIdentity(this, identity, caState.issuerCertificate, caState.signingKey);
+ }
+
+ public KeyStore getBeetlesKeyStore() {
+ return loadKeyStore(new File(workingDir, BEETLES_STORE));
+ }
+
+ public String getKeyStoreType() {
+ return KEYSTORE_TYPE;
+ }
+
+ /**
+ * @deprecated Use {@link CommonIdentity#getCertificate()} instead.
+ */
+ @Deprecated()
public X509Certificate getCertificate(final Identity identity) {
return certificateMap.get(identity);
}
+ /**
+ * @deprecated Use {@link DefinedCAIdentity#getPrivateKey()} instead.
+ */
+ @Deprecated()
public PrivateKey getPrivateKey(final Identity identity) {
if (!identity.isCertificateAuthority()) {
- throw new IllegalStateException(String.format("Identity %s if not a CertificateAuthority", identity.toString()));
+ throw new IllegalStateException(
+ String.format("Identity %s if not a CertificateAuthority", identity.toString()));
}
return caMap.computeIfAbsent(identity, this::createCA).signingKey;
@@ -126,7 +175,8 @@ private CAState createCA(final Identity identity) {
Identity signedBy = identity.getSignedBy();
if (signedBy == null) {
// As a root CA it will require a self signed certificate.
- SelfSignedX509CertificateAndSigningKey issuerSelfSignedX509CertificateAndSigningKey = SelfSignedX509CertificateAndSigningKey.builder()
+ SelfSignedX509CertificateAndSigningKey issuerSelfSignedX509CertificateAndSigningKey = SelfSignedX509CertificateAndSigningKey
+ .builder()
.setDn(identity.getPrincipal())
.setKeyAlgorithmName(KEY_ALGORITHM)
.setSignatureAlgorithmName(SIGNATURE_ALGORTHM)
@@ -147,8 +197,8 @@ private CAState createCA(final Identity identity) {
.setSerialNumber(BigInteger.valueOf(signerState.serialNumber++))
.addExtension(new BasicConstraintsExtension(false, true, -1))
.addExtension(new AuthorityInformationAccessExtension(Collections.singletonList(
- new AccessDescription(OID_AD_OCSP, new GeneralName.URIName("http://localhost:" + OCSP_PORT + "/ocsp"))
- )))
+ new AccessDescription(OID_AD_OCSP,
+ new GeneralName.URIName("http://localhost:" + OCSP_PORT + "/ocsp")))))
.build();
caState.issuerCertificate = intermediateIssuerCertificate;
@@ -175,24 +225,72 @@ private CAState createCA(final Identity identity) {
return caState;
}
- public X509Certificate createIdentity(final String alias, final X500Principal principal, final String keyStoreName,
- final Identity ca, final X509CertificateExtension... extensions) {
- KeyPair keyPair = keyPairGenerator.generateKeyPair();
+ private X509Certificate createCustomCertificate(final Identity ca, final X500Principal principal,
+ final KeyPair keyPair, final X509CertificateExtension... extensions) throws CertificateException{
+
CAState caState = caMap.computeIfAbsent(ca, this::createCA);
+ X509CertificateBuilder certificateBuilder = new X509CertificateBuilder()
+ .setIssuerDn(ca.getPrincipal())
+ .setSubjectDn(principal)
+ .setSignatureAlgorithmName(SIGNATURE_ALGORTHM)
+ .setSigningKey(caState.signingKey)
+ .setPublicKey(keyPair.getPublic())
+ .setSerialNumber(BigInteger.valueOf(caState.serialNumber++))
+ .addExtension(new BasicConstraintsExtension(false, false, -1));
+ for (X509CertificateExtension currentExtension : extensions) {
+ certificateBuilder.addExtension(currentExtension);
+ }
+
+ return certificateBuilder.build();
+ }
+
+ CustomIdentity createCustomIdentity(final String alias, final X500Principal principal, final String keyStoreName,
+ final Identity ca, final X509CertificateExtension... extensions) {
try {
- X509CertificateBuilder certificateBuilder = new X509CertificateBuilder()
- .setIssuerDn(ca.getPrincipal())
- .setSubjectDn(principal)
- .setSignatureAlgorithmName(SIGNATURE_ALGORTHM)
- .setSigningKey(caState.signingKey)
- .setPublicKey(keyPair.getPublic())
- .setSerialNumber(BigInteger.valueOf(caState.serialNumber++))
- .addExtension(new BasicConstraintsExtension(false, false, -1));
- for (X509CertificateExtension currentExtension : extensions) {
- certificateBuilder.addExtension(currentExtension);
+ KeyPair keyPair = keyPairGenerator.generateKeyPair();
+ X509Certificate builtCertificate = createCustomCertificate(ca, principal, keyPair, extensions);
+
+ File keyStoreFile = new File(workingDir, keyStoreName);
+ KeyStore keyStore = createEmptyKeyStore();
+
+ List certificates = new ArrayList<>();
+ certificates.add(builtCertificate);
+
+ Identity caIdentity = ca;
+ CAState caState;
+
+ do {
+ caState = caMap.get(caIdentity); // We just created a signed cert above, the complete chain must be
+ // present.
+ certificates.add(caState.issuerCertificate);
+ caIdentity = caIdentity.getSignedBy();
+ } while (caIdentity != null);
+
+ keyStore.setKeyEntry(alias, keyPair.getPrivate(), PASSWORD,
+ certificates.toArray(new X509Certificate[certificates.size()]));
+ try (OutputStream out = new FileOutputStream(keyStoreFile)) {
+ keyStore.store(out, PASSWORD);
}
- X509Certificate builtCertificate = certificateBuilder.build();
+
+ return new CustomIdentity(this, builtCertificate, keyStoreFile);
+
+ } catch (IOException | KeyStoreException | CertificateException | NoSuchAlgorithmException e) {
+ throw new RuntimeException("Umnable to create identity", e);
+ }
+ }
+
+ /**
+ * @deprecated Use
+ * {@link #createIdentity(String, X500Principal, String, X509CertificateExtension...)}
+ * instead.
+ */
+ @Deprecated
+ public X509Certificate createIdentity(final String alias, final X500Principal principal, final String keyStoreName,
+ final Identity ca, final X509CertificateExtension... extensions) {
+ try {
+ KeyPair keyPair = keyPairGenerator.generateKeyPair();
+ X509Certificate builtCertificate = createCustomCertificate(ca, principal, keyPair, extensions);
File keyStoreFile = new File(workingDir, keyStoreName);
KeyStore keyStore = createEmptyKeyStore();
@@ -201,9 +299,14 @@ public X509Certificate createIdentity(final String alias, final X500Principal pr
certificates.add(builtCertificate);
Identity caIdentity = ca;
+ CAState caState;
+
do {
- caState = caMap.get(caIdentity); // We just created a signed cert above, the complete chain must be present.
- keyStore.setCertificateEntry(caIdentity.toString(), caState.issuerCertificate); // This could be removed as the cert chain is added to the Entry.
+ caState = caMap.get(caIdentity); // We just created a signed cert above, the complete chain must be
+ // present.
+ keyStore.setCertificateEntry(caIdentity.toString(), caState.issuerCertificate); // This could be removed
+ // as the cert chain is
+ // added to the Entry.
certificates.add(caState.issuerCertificate);
caIdentity = caIdentity.getSignedBy();
} while (caIdentity != null);
@@ -220,7 +323,8 @@ public X509Certificate createIdentity(final String alias, final X500Principal pr
}
}
- public X509Certificate createSelfSignedIdentity(final String alias, final X500Principal principal, final String keyStoreName) {
+ private X509Certificate createSelfSignedIdentity(final String alias, final X500Principal principal,
+ final String keyStoreName) {
SelfSignedX509CertificateAndSigningKey selfSignedIdentity = SelfSignedX509CertificateAndSigningKey.builder()
.setDn(principal)
.setKeyAlgorithmName(KEY_ALGORITHM)
@@ -256,7 +360,7 @@ private X509Certificate createIdentity(final Identity identity) {
private static KeyStore createEmptyKeyStore() {
try {
KeyStore ks = KeyStore.getInstance(KEYSTORE_TYPE);
- ks.load(null,null);
+ ks.load(null, null);
return ks;
} catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e) {
@@ -264,7 +368,15 @@ private static KeyStore createEmptyKeyStore() {
}
}
- private static KeyStore loadKeyStore(final File location) {
+ File getKeyStoreFile(Identity identity) {
+ return new File(workingDir, identity.getKeyStoreName());
+ }
+
+ KeyStore loadKeyStore(final Identity identity) {
+ return loadKeyStore(getKeyStoreFile(identity));
+ }
+
+ static KeyStore loadKeyStore(final File location) {
try (InputStream caTrustStoreFile = new FileInputStream(location)) {
KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE);
keyStore.load(caTrustStoreFile, PASSWORD);
@@ -275,8 +387,15 @@ private static KeyStore loadKeyStore(final File location) {
}
}
+ void assertNotClosed() {
+ if (closed) {
+ throw new IllegalStateException("The CAGenerationTool is closed.");
+ }
+ }
+
@Override
public void close() throws IOException {
+ closed = true;
workingDir.delete();
}
@@ -306,7 +425,8 @@ public enum Identity {
CA, true, null),
ROVE("OU=Elytron, O=Elytron, C=UK, ST=Elytron, CN=Rove",
INTERMEDIATE, false, "rove.keystore"),
- SECOND_CA("CN=Wildfly CA, ST=Wildfly, C=CA, EMAILADDRESS=admin@wildfly.org O=Another Root Certificate Authority",
+ SECOND_CA(
+ "CN=Wildfly CA, ST=Wildfly, C=CA, EMAILADDRESS=admin@wildfly.org O=Another Root Certificate Authority",
null, true, "ca.truststore2"),
LADYBUG("OU=Wildfly, O=Wildfly, C=CA, ST=Wildfly, CN=Ladybug", SECOND_CA, false,
"ladybug.keystore"),
@@ -318,8 +438,9 @@ public enum Identity {
private final boolean ca;
private final String keyStoreName;
- private Identity(final String distinguishedName, final Identity signedBy, final boolean ca, final String keyStoreName) {
- this.principal = new X500Principal(distinguishedName);
+ private Identity(final String distinguishedName, final Identity signedBy, final boolean ca,
+ final String keyStoreName) {
+ this.principal = new X500Principal(distinguishedName);
this.signedBy = signedBy;
this.ca = ca;
this.keyStoreName = keyStoreName;
diff --git a/tests/common/src/test/java/org/wildfly/security/ssl/test/util/CommonIdentity.java b/tests/common/src/test/java/org/wildfly/security/ssl/test/util/CommonIdentity.java
new file mode 100644
index 00000000000..ffd89fbdf0a
--- /dev/null
+++ b/tests/common/src/test/java/org/wildfly/security/ssl/test/util/CommonIdentity.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2024 Red Hat, Inc.
+ *
+ * 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.ssl.test.util;
+
+import java.io.File;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.X509ExtendedKeyManager;
+
+public abstract class CommonIdentity {
+
+ protected final CAGenerationTool caGenerationTool;
+ private final X509Certificate certificate;
+
+ CommonIdentity(CAGenerationTool caGenerationTool, X509Certificate certificate) {
+ this.caGenerationTool = caGenerationTool;
+ this.certificate = certificate;
+ }
+
+ public X509Certificate getCertificate() {
+ caGenerationTool.assertNotClosed();
+
+ return certificate;
+ }
+
+ public String getKeyStoreType() {
+ return caGenerationTool.getKeyStoreType();
+ }
+
+ public abstract KeyStore loadKeyStore();
+
+ public abstract File getKeyStoreFile();
+
+ public X509ExtendedKeyManager createKeyManager() {
+ caGenerationTool.assertNotClosed();
+
+ try {
+ KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
+ keyManagerFactory.init(loadKeyStore(), CAGenerationTool.PASSWORD);
+
+ for (KeyManager current : keyManagerFactory.getKeyManagers()) {
+ if (current instanceof X509ExtendedKeyManager) {
+ return (X509ExtendedKeyManager) current;
+ }
+ }
+ } catch (NoSuchAlgorithmException | KeyStoreException | UnrecoverableKeyException e) {
+ throw new IllegalStateException("Unable to obtain X509ExtendedKeyManager.", e);
+ }
+
+ throw new IllegalStateException("Unable to obtain X509ExtendedKeyManager.");
+ }
+
+}
diff --git a/tests/common/src/test/java/org/wildfly/security/ssl/test/util/CustomIdentity.java b/tests/common/src/test/java/org/wildfly/security/ssl/test/util/CustomIdentity.java
new file mode 100644
index 00000000000..f7f2100c53f
--- /dev/null
+++ b/tests/common/src/test/java/org/wildfly/security/ssl/test/util/CustomIdentity.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2024 Red Hat, Inc.
+ *
+ * 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.ssl.test.util;
+
+import java.io.File;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+
+public class CustomIdentity extends CommonIdentity {
+
+ private final File keyStoreFile;
+
+ CustomIdentity(CAGenerationTool caGenerationTool, X509Certificate certificate, File keyStoreFile) {
+ super(caGenerationTool, certificate);
+ this.keyStoreFile = keyStoreFile;
+ }
+
+ @Override
+ public KeyStore loadKeyStore() {
+ return CAGenerationTool.loadKeyStore(keyStoreFile);
+ }
+
+ @Override
+ public File getKeyStoreFile() {
+ return keyStoreFile;
+ }
+
+}
diff --git a/tests/common/src/test/java/org/wildfly/security/ssl/test/util/DefinedCAIdentity.java b/tests/common/src/test/java/org/wildfly/security/ssl/test/util/DefinedCAIdentity.java
new file mode 100644
index 00000000000..4b01b4af6c8
--- /dev/null
+++ b/tests/common/src/test/java/org/wildfly/security/ssl/test/util/DefinedCAIdentity.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2024 Red Hat, Inc.
+ *
+ * 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.ssl.test.util;
+
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+import javax.security.auth.x500.X500Principal;
+
+import org.wildfly.security.ssl.test.util.CAGenerationTool.Identity;
+import org.wildfly.security.x500.cert.X509CertificateExtension;
+
+public class DefinedCAIdentity extends DefinedIdentity {
+
+ private final PrivateKey privateKey;
+
+ DefinedCAIdentity(CAGenerationTool caGenerationTool, Identity identity,
+ X509Certificate certificate, PrivateKey privateKey) {
+ super(caGenerationTool, identity, certificate);
+ this.privateKey = privateKey;
+ }
+
+ public CustomIdentity createIdentity(final String alias, final X500Principal principal,
+ final String keyStoreName, final X509CertificateExtension... extensions) {
+ caGenerationTool.assertNotClosed();
+
+ return caGenerationTool.createCustomIdentity(alias, principal, keyStoreName, identity, extensions);
+ }
+
+
+ public PrivateKey getPrivateKey() {
+ caGenerationTool.assertNotClosed();
+
+ return privateKey;
+ }
+
+ public X509TrustManager createTrustManager() {
+ caGenerationTool.assertNotClosed();
+
+ try {
+ TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("PKIX");
+ trustManagerFactory.init(caGenerationTool.loadKeyStore(identity));
+
+ for (TrustManager current : trustManagerFactory.getTrustManagers()) {
+ if (current instanceof X509TrustManager) {
+ return (X509TrustManager) current;
+ }
+ }
+ } catch (NoSuchAlgorithmException | KeyStoreException e) {
+ throw new IllegalStateException("Unable to obtain X509TrustManager.", e);
+ }
+
+ throw new IllegalStateException("Unable to obtain X509TrustManager.");
+ }
+}
diff --git a/tests/common/src/test/java/org/wildfly/security/ssl/test/util/DefinedIdentity.java b/tests/common/src/test/java/org/wildfly/security/ssl/test/util/DefinedIdentity.java
new file mode 100644
index 00000000000..0983b2e928d
--- /dev/null
+++ b/tests/common/src/test/java/org/wildfly/security/ssl/test/util/DefinedIdentity.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2024 Red Hat, Inc.
+ *
+ * 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.ssl.test.util;
+
+import java.io.File;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+
+import org.wildfly.security.ssl.test.util.CAGenerationTool.Identity;
+
+public class DefinedIdentity extends CommonIdentity {
+
+ protected final Identity identity;
+
+ DefinedIdentity(CAGenerationTool caGenerationTool,
+ Identity identity,
+ X509Certificate certificate) {
+ super(caGenerationTool, certificate);
+ this.identity = identity;
+ }
+
+ public KeyStore loadKeyStore() {
+ caGenerationTool.assertNotClosed();
+
+ return caGenerationTool.loadKeyStore(identity);
+ }
+
+ @Override
+ public File getKeyStoreFile() {
+ return caGenerationTool.getKeyStoreFile(identity);
+ }
+
+}
diff --git a/tool/pom.xml b/tool/pom.xml
index f19c2a561ef..6cf2c73c159 100644
--- a/tool/pom.xml
+++ b/tool/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
4.0.0
@@ -304,6 +304,10 @@
jboss-logging-processor
provided
+
+ org.aesh
+ aesh
+
diff --git a/tool/src/main/java/org/wildfly/security/tool/CredentialStoreCommand.java b/tool/src/main/java/org/wildfly/security/tool/CredentialStoreCommand.java
index 62b2637a773..3f6d765d73a 100644
--- a/tool/src/main/java/org/wildfly/security/tool/CredentialStoreCommand.java
+++ b/tool/src/main/java/org/wildfly/security/tool/CredentialStoreCommand.java
@@ -46,7 +46,6 @@
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
-import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionGroup;
import org.apache.commons.cli.Options;
@@ -69,6 +68,10 @@
import org.wildfly.security.password.interfaces.ClearPassword;
import org.wildfly.security.pem.Pem;
import org.wildfly.security.ssh.util.SshUtil;
+import org.wildfly.security.tool.help.DescriptionSection;
+import org.wildfly.security.tool.help.HelpCommand;
+import org.wildfly.security.tool.help.OptionsSection;
+import org.wildfly.security.tool.help.UsageSection;
import static org.wildfly.security.tool.Params.ALIAS_PARAM;
import static org.wildfly.security.tool.Params.CREATE_CREDENTIAL_STORE_PARAM;
@@ -92,8 +95,8 @@
*/
class CredentialStoreCommand extends Command {
- public static int ACTION_NOT_DEFINED = 5;
- public static int ALIAS_NOT_FOUND = 6;
+ public static final int ACTION_NOT_DEFINED = 5;
+ public static final int ALIAS_NOT_FOUND = 6;
public static final String RSA_ALGORITHM = "RSA";
public static final String DSA_ALGORITHM = "DSA";
@@ -959,13 +962,15 @@ protected Set aliases() {
*/
@Override
public void help() {
- HelpFormatter help = new HelpFormatter();
- help.setWidth(WIDTH);
- help.printHelp(ElytronToolMessages.msg.cmdHelp(getToolCommand(), CREDENTIAL_STORE_COMMAND),
- ElytronToolMessages.msg.cmdLineCredentialStoreHelpHeader().concat(ElytronToolMessages.msg.cmdLineActionsHelpHeader()),
- options,
- "",
- true);
+ OptionsSection optionsSection = new OptionsSection(ElytronToolMessages.msg.cmdLineActionsHelpHeader(), options);
+ UsageSection usageSection = new UsageSection(CREDENTIAL_STORE_COMMAND, null);
+ DescriptionSection descriptionSection = new DescriptionSection(ElytronToolMessages.msg.cmdLineCredentialStoreHelpHeader());
+ HelpCommand helpCommand = HelpCommand.HelpCommandBuilder.builder()
+ .description(descriptionSection)
+ .usage(usageSection)
+ .options(optionsSection)
+ .build();
+ helpCommand.printHelp();
}
static Map parseCredentialStoreProperties(final String attributeString) {
diff --git a/tool/src/main/java/org/wildfly/security/tool/ElytronTool.java b/tool/src/main/java/org/wildfly/security/tool/ElytronTool.java
index 0d0d4193a10..1111c9ad6af 100644
--- a/tool/src/main/java/org/wildfly/security/tool/ElytronTool.java
+++ b/tool/src/main/java/org/wildfly/security/tool/ElytronTool.java
@@ -20,10 +20,17 @@
import org.apache.commons.cli.AlreadySelectedException;
import org.apache.commons.cli.Option;
import org.wildfly.security.WildFlyElytronProvider;
+import org.wildfly.security.tool.help.CommandsSection;
+import org.wildfly.security.tool.help.DescriptionSection;
+import org.wildfly.security.tool.help.HelpCommand;
+import org.wildfly.security.tool.help.OptionsSection;
+import org.wildfly.security.tool.help.UsageSection;
import java.security.Security;
import java.util.HashMap;
import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
/**
* Elytron Tool main class which drives all registered commands.
@@ -36,11 +43,11 @@ public class ElytronTool {
/**
* status code for unrecognized command
*/
- public static int ElytronToolExitStatus_unrecognizedCommand = 1;
+ public static final int ElytronToolExitStatus_unrecognizedCommand = 1;
/**
* status code for no problems
*/
- public static int ElytronToolExitStatus_OK = 0;
+ public static final int ElytronToolExitStatus_OK = 0;
private Map commandRegistry = new HashMap<>();
/**
@@ -131,15 +138,27 @@ private static void configureLogManager() {
}
private void generalHelp() {
- System.out.print(ElytronToolMessages.msg.generalHelpTitle());
- System.out.println();
- for (Command c: commandRegistry.values()) {
- if (scriptName != null) {
- c.setToolCommand(scriptName);
- }
- c.help();
- System.out.println();
- }
+ DescriptionSection descriptionSection = new DescriptionSection(ElytronToolMessages.msg.cmdElytronToolDescription());
+ UsageSection usageSection = new UsageSection(null, null);
+ OptionsSection optionsSection = new OptionsSection(ElytronToolMessages.msg.generalHelpOptionsOpening(), null);
+
+ // Using SortedMap so commands are in alphabetical order
+ SortedMap commandsMap = new TreeMap<>();
+ commandsMap.put(CredentialStoreCommand.CREDENTIAL_STORE_COMMAND, ElytronToolMessages.msg.cmdLineCredentialStoreHelpHeader());
+ commandsMap.put(VaultCommand.VAULT_COMMAND, ElytronToolMessages.msg.cmdVaultHelpHeader());
+ commandsMap.put(FileSystemRealmCommand.FILE_SYSTEM_REALM_COMMAND, ElytronToolMessages.msg.cmdFileSystemRealmHelpHeader());
+ commandsMap.put(FileSystemEncryptRealmCommand.FILE_SYSTEM_ENCRYPT_COMMAND, ElytronToolMessages.msg.cmdFileSystemEncryptHelpHeader());
+ commandsMap.put(MaskCommand.MASK_COMMAND, ElytronToolMessages.msg.cmdMaskHelpHeader());
+ commandsMap.put(FileSystemRealmIntegrityCommand.FILE_SYSTEM_REALM_INTEGRITY_COMMAND, ElytronToolMessages.msg.cmdFileSystemIntegrityHelpHeader());
+ CommandsSection commandsSection = new CommandsSection(commandsMap);
+
+ HelpCommand helpCommand = HelpCommand.HelpCommandBuilder.builder()
+ .description(descriptionSection)
+ .usage(usageSection)
+ .options(optionsSection)
+ .commands(commandsSection)
+ .build();
+ helpCommand.printHelp();
}
Command findCommand(String commandName) {
diff --git a/tool/src/main/java/org/wildfly/security/tool/ElytronToolMessages.java b/tool/src/main/java/org/wildfly/security/tool/ElytronToolMessages.java
index 6a023c4f1d2..4b876d33b25 100644
--- a/tool/src/main/java/org/wildfly/security/tool/ElytronToolMessages.java
+++ b/tool/src/main/java/org/wildfly/security/tool/ElytronToolMessages.java
@@ -105,25 +105,25 @@ public interface ElytronToolMessages extends BasicLogger {
"Provider must be installed through java.security file or through service loader from properly packaged jar file on classpath.")
String cmdLineCustomCredentialStoreProviderDesc();
- @Message(id = NONE, value = "Create credential store (Action)")
+ @Message(id = NONE, value = "* Create credential store")
String cmdLineCreateCredentialStoreDesc();
@Message(id = NONE, value = "Credential store type")
String cmdLineCredentialStoreTypeDesc();
- @Message(id = NONE, value = "Add new alias to the credential store (Action)")
+ @Message(id = NONE, value = "* Add new alias to the credential store")
String cmdLineAddAliasDesc();
- @Message(id = NONE, value = "Remove alias from the credential store (Action)")
+ @Message(id = NONE, value = "* Remove alias from the credential store")
String cmdLineRemoveAliasDesc();
- @Message(id = NONE, value = "Check if alias exists within the credential store (Action)")
+ @Message(id = NONE, value = "* Check if alias exists within the credential store")
String cmdLineCheckAliasDesc();
- @Message(id = NONE, value = "Display all aliases (Action)")
+ @Message(id = NONE, value = "* Display all aliases")
String cmdLineAliasesDesc();
- @Message(id = NONE, value = "Display all types of stored credentials for given alias (Action)")
+ @Message(id = NONE, value = "* Display all types of stored credentials for given alias")
String cmdLineAliasTypes();
@Message(id = NONE, value = "Generate private and public key pair and store them as a KeyPairCredential")
@@ -159,7 +159,7 @@ public interface ElytronToolMessages extends BasicLogger {
@Message(id = NONE, value = "Print summary, especially command how to create this credential store")
String cmdLinePrintSummary();
- @Message(id = NONE, value = "Get help with usage of this command (Action)")
+ @Message(id = NONE, value = "* Get help with usage of this command")
String cmdLineHelp();
@Message(id = NONE, value = "Alias \"%s\" exists")
@@ -281,7 +281,7 @@ public interface ElytronToolMessages extends BasicLogger {
@Message(id = NONE, value = "CLI command to add new credential store:%n")
String cliCommandToNewCredentialStore();
- @Message(id = NONE, value = "Bulk conversion with options listed in description file. All options have no default value and should be set in the file. (Action)%n" +
+ @Message(id = NONE, value = "* Bulk conversion with options listed in description file. All options have no default value and should be set in the file.%n" +
"All options are required with the exceptions:%n" +
" - \"properties\" option%n - \"type\" option (defaults to \"KeyStoreCredentialStore\")%n - \"credential-store-provider\" option%n - \"other-providers\" option%n" +
" - \"salt\" and \"iteration\" options can be omitted when plain-text password is used%n" +
@@ -406,7 +406,7 @@ public interface ElytronToolMessages extends BasicLogger {
String longOptionDescription(String option, String longOption);
// filesystem-realm command
- @Message(id = NONE, value = "'FileSystemRealm' command is used to convert legacy properties files and scripts to an Elytron FileSystemRealm.")
+ @Message(id = NONE, value = "\"filesystem-realm\" command is used to convert legacy properties files and scripts to an Elytron FileSystemRealm.")
String cmdFileSystemRealmHelpHeader();
@Message(id = NONE, value = "The relative or absolute path to the users file.")
@@ -489,7 +489,7 @@ public interface ElytronToolMessages extends BasicLogger {
@Message(id = NONE, value = "Name of the security-domain to be configured.")
String cmdFileSystemRealmSecurityDomainNameDesc();
- @Message(id = NONE, value = "Bulk conversion with options listed in description file. Optional options have default values, required options do not. (Action) %n" +
+ @Message(id = NONE, value = "* Bulk conversion with options listed in description file. Optional options have default values, required options do not.%n" +
"The options fileSystemRealmName and securityDomainName are optional. %n" +
"These optional options have default values of: converted-properties-filesystem-realm and converted-properties-security-domain. %n" +
"Values are required for the following options: users-file, roles-file, and output-location. %n" +
@@ -498,7 +498,7 @@ public interface ElytronToolMessages extends BasicLogger {
"Blocks of options must be separated by a blank line.")
String cmdFileSystemRealmBulkConvertDesc();
- @Message(id = NONE, value = "Bulk conversion with options listed in description file. Optional options have default values, required options do not. (Action) %n" +
+ @Message(id = NONE, value = "* Bulk conversion with options listed in description file. Optional options have default values, required options do not. %n" +
"The options realm-name, hash-encoding, levels, secret-key, create, populate, keystore, type, password, password-env, and key-pair are optional. %n" +
"Values are required for the following options: input-location, output-location, and credential-store. %n" +
"The default values of realm-name, hash-encoding, hash-charset, levels, secret-key, create, and populate are encrypted-filesystem-realm, BASE64, UTF-8, 2, key, true, and true respectively. %n" +
@@ -508,7 +508,7 @@ public interface ElytronToolMessages extends BasicLogger {
"Blocks of options must be separated by a blank line.")
String cmdFileSystemRealmEncryptBulkConvertDesc();
- @Message(id = NONE, value = "Bulk conversion with options listed in description file. (Action)" +
+ @Message(id = NONE, value = "* Bulk conversion with options listed in description file. " +
"Optional options have defaults and can be skipped ([type, default_or_NULL]), required options do not (). %n" +
"One of either password or password-env is required. %n" +
"Blocks of options must be separated by a blank line; order is not important. Syntax: %n" +
@@ -519,7 +519,7 @@ public interface ElytronToolMessages extends BasicLogger {
String cmdFileSystemRealmIntegrityBulkConvertDesc();
// filesystem-realm encrypt command
- @Message(id = NONE, value = "'FileSystemRealmEncrypt' command is used to convert non-empty, un-encrypted FileSystemSecurityRealm(s) to encrypted FileSystemSecurityRealm(s) with a SecretKey.")
+ @Message(id = NONE, value = "\"filesystem-realm-encrypt\" command is used to convert non-empty, un-encrypted FileSystemSecurityRealm(s) to encrypted FileSystemSecurityRealm(s) with a SecretKey.")
String cmdFileSystemEncryptHelpHeader();
@Message(id = NONE, value = "Secret Key was not found in the Credential Store at %s, and populate option was not set. Skipping descriptor file block number %d.")
@@ -669,7 +669,7 @@ public interface ElytronToolMessages extends BasicLogger {
@Message(id = NONE, value = "Should file %s be overwritten? (y/n) ")
String shouldFileBeOverwritten(String file);
- @Message(id = NONE, value = "\nSome of the parameters below are mutually exclusive actions which are marked with (Action) in the description.")
+ @Message(id = NONE, value = "Some of the parameters below are mutually exclusive actions which are marked with * in the description.")
String cmdLineActionsHelpHeader();
@Message(id = NONE, value = "Key size (bits).")
@@ -738,8 +738,13 @@ public interface ElytronToolMessages extends BasicLogger {
@Message(id = NONE, value = "No Credential Store location or Secret Key Alias specified.")
MissingOptionException missingCredentialStoreSecretKey();
+ @Message(id = NONE, value = "To get list of options for a specific command, please specify the command by using ./elytron-tool.sh [command] --help")
+ String generalHelpOptionsOpening();
+
+ @Message(id = NONE, value = "A tool that assists with Elytron configuration")
+ String cmdElytronToolDescription();
+
// Numeric Errors
@Message(id = 35, value = "Only one of '%s' and '%s' can be specified at the same time")
IllegalArgumentException mutuallyExclusiveOptions(String first, String second);
-
}
diff --git a/tool/src/main/java/org/wildfly/security/tool/FileSystemEncryptRealmCommand.java b/tool/src/main/java/org/wildfly/security/tool/FileSystemEncryptRealmCommand.java
index c5e858827a4..844332d76de 100644
--- a/tool/src/main/java/org/wildfly/security/tool/FileSystemEncryptRealmCommand.java
+++ b/tool/src/main/java/org/wildfly/security/tool/FileSystemEncryptRealmCommand.java
@@ -65,7 +65,6 @@
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
-import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionGroup;
import org.apache.commons.cli.Options;
@@ -73,6 +72,10 @@
import org.wildfly.security.auth.realm.FileSystemSecurityRealm;
import org.wildfly.security.auth.realm.FileSystemSecurityRealmBuilder;
import org.wildfly.security.password.spec.Encoding;
+import org.wildfly.security.tool.help.DescriptionSection;
+import org.wildfly.security.tool.help.HelpCommand;
+import org.wildfly.security.tool.help.OptionsSection;
+import org.wildfly.security.tool.help.UsageSection;
/**
* Elytron-Tool command to convert un-encrypted FileSystemRealms into an encrypted realm with the use of a SecretKey.
@@ -576,13 +579,15 @@ public void execute(String[] args) throws Exception {
*/
@Override
public void help() {
- HelpFormatter help = new HelpFormatter();
- help.setWidth(WIDTH);
- help.printHelp(ElytronToolMessages.msg.cmdHelp(getToolCommand(), FILE_SYSTEM_ENCRYPT_COMMAND),
- ElytronToolMessages.msg.cmdFileSystemEncryptHelpHeader(),
- options,
- "",
- true);
+ OptionsSection optionsSection = new OptionsSection(ElytronToolMessages.msg.cmdLineActionsHelpHeader(), options);
+ UsageSection usageSection = new UsageSection(FILE_SYSTEM_ENCRYPT_COMMAND, null);
+ DescriptionSection descriptionSection = new DescriptionSection(ElytronToolMessages.msg.cmdFileSystemEncryptHelpHeader());
+ HelpCommand helpCommand = HelpCommand.HelpCommandBuilder.builder()
+ .description(descriptionSection)
+ .usage(usageSection)
+ .options(optionsSection)
+ .build();
+ helpCommand.printHelp();
}
/**
diff --git a/tool/src/main/java/org/wildfly/security/tool/FileSystemRealmCommand.java b/tool/src/main/java/org/wildfly/security/tool/FileSystemRealmCommand.java
index 2bf21d4e484..aff167ece0b 100644
--- a/tool/src/main/java/org/wildfly/security/tool/FileSystemRealmCommand.java
+++ b/tool/src/main/java/org/wildfly/security/tool/FileSystemRealmCommand.java
@@ -48,7 +48,6 @@
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
-import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.lang3.ArrayUtils;
@@ -63,6 +62,10 @@
import org.wildfly.security.password.interfaces.DigestPassword;
import org.wildfly.security.password.spec.DigestPasswordSpec;
import org.wildfly.security.password.spec.PasswordSpec;
+import org.wildfly.security.tool.help.DescriptionSection;
+import org.wildfly.security.tool.help.HelpCommand;
+import org.wildfly.security.tool.help.OptionsSection;
+import org.wildfly.security.tool.help.UsageSection;
/**
* Elytron-Tool command to convert legacy properties file into a FileSystemRealm.
@@ -298,13 +301,15 @@ public void execute(String[] args) throws Exception {
*/
@Override
public void help() {
- HelpFormatter help = new HelpFormatter();
- help.setWidth(WIDTH);
- help.printHelp(ElytronToolMessages.msg.cmdHelp(getToolCommand(), FILE_SYSTEM_REALM_COMMAND),
- ElytronToolMessages.msg.cmdFileSystemRealmHelpHeader().concat(ElytronToolMessages.msg.cmdLineActionsHelpHeader()),
- options,
- "",
- true);
+ OptionsSection optionsSection = new OptionsSection(ElytronToolMessages.msg.cmdLineActionsHelpHeader(), options);
+ UsageSection usageSection = new UsageSection(FILE_SYSTEM_REALM_COMMAND, null);
+ DescriptionSection descriptionSection = new DescriptionSection(ElytronToolMessages.msg.cmdFileSystemRealmHelpHeader());
+ HelpCommand helpCommand = HelpCommand.HelpCommandBuilder.builder()
+ .description(descriptionSection)
+ .usage(usageSection)
+ .options(optionsSection)
+ .build();
+ helpCommand.printHelp();
}
@Override
diff --git a/tool/src/main/java/org/wildfly/security/tool/FileSystemRealmIntegrityCommand.java b/tool/src/main/java/org/wildfly/security/tool/FileSystemRealmIntegrityCommand.java
index 176b824dfd1..33de909669f 100644
--- a/tool/src/main/java/org/wildfly/security/tool/FileSystemRealmIntegrityCommand.java
+++ b/tool/src/main/java/org/wildfly/security/tool/FileSystemRealmIntegrityCommand.java
@@ -72,7 +72,6 @@
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
-import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionGroup;
import org.apache.commons.cli.Options;
@@ -80,6 +79,10 @@
import org.wildfly.security.auth.realm.FileSystemSecurityRealm;
import org.wildfly.security.auth.realm.FileSystemSecurityRealmBuilder;
import org.wildfly.security.password.spec.Encoding;
+import org.wildfly.security.tool.help.DescriptionSection;
+import org.wildfly.security.tool.help.HelpCommand;
+import org.wildfly.security.tool.help.OptionsSection;
+import org.wildfly.security.tool.help.UsageSection;
/**
* Elytron Tool command to enable integrity checking in filesystem realms that previously did not have it enabled. If
@@ -610,13 +613,15 @@ public void execute(String[] args) throws Exception {
/** Displays the help screen for the command */
@Override
public void help() {
- HelpFormatter help = new HelpFormatter();
- help.setWidth(WIDTH);
- help.printHelp(ElytronToolMessages.msg.cmdHelp(getToolCommand(), FILE_SYSTEM_REALM_INTEGRITY_COMMAND),
- ElytronToolMessages.msg.cmdFileSystemIntegrityHelpHeader(),
- options,
- "",
- true);
+ OptionsSection optionsSection = new OptionsSection(ElytronToolMessages.msg.cmdLineActionsHelpHeader(), options);
+ UsageSection usageSection = new UsageSection(FILE_SYSTEM_REALM_INTEGRITY_COMMAND, null);
+ DescriptionSection descriptionSection = new DescriptionSection(ElytronToolMessages.msg.cmdFileSystemIntegrityHelpHeader());
+ HelpCommand helpCommand = HelpCommand.HelpCommandBuilder.builder()
+ .description(descriptionSection)
+ .usage(usageSection)
+ .options(optionsSection)
+ .build();
+ helpCommand.printHelp();
}
/**
diff --git a/tool/src/main/java/org/wildfly/security/tool/MaskCommand.java b/tool/src/main/java/org/wildfly/security/tool/MaskCommand.java
index bf029f0e6ee..154a95d9582 100644
--- a/tool/src/main/java/org/wildfly/security/tool/MaskCommand.java
+++ b/tool/src/main/java/org/wildfly/security/tool/MaskCommand.java
@@ -23,9 +23,12 @@
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
-import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
+import org.wildfly.security.tool.help.DescriptionSection;
+import org.wildfly.security.tool.help.HelpCommand;
+import org.wildfly.security.tool.help.OptionsSection;
+import org.wildfly.security.tool.help.UsageSection;
import org.wildfly.security.util.PasswordBasedEncryptionUtil;
import static org.wildfly.security.tool.Params.DEBUG_PARAM;
@@ -158,12 +161,14 @@ static char[] decryptMasked(String maskedPassword) throws GeneralSecurityExcepti
*/
@Override
public void help() {
- HelpFormatter help = new HelpFormatter();
- help.setWidth(WIDTH);
- help.printHelp(ElytronToolMessages.msg.cmdHelp(getToolCommand(), MASK_COMMAND),
- ElytronToolMessages.msg.cmdMaskHelpHeader().concat(ElytronToolMessages.msg.cmdLineActionsHelpHeader()),
- options,
- "",
- true);
+ OptionsSection optionsSection = new OptionsSection(ElytronToolMessages.msg.cmdLineActionsHelpHeader(), options);
+ UsageSection usageSection = new UsageSection(MASK_COMMAND, null);
+ DescriptionSection descriptionSection = new DescriptionSection(ElytronToolMessages.msg.cmdMaskHelpHeader());
+ HelpCommand helpCommand = HelpCommand.HelpCommandBuilder.builder()
+ .description(descriptionSection)
+ .usage(usageSection)
+ .options(optionsSection)
+ .build();
+ helpCommand.printHelp();
}
}
diff --git a/tool/src/main/java/org/wildfly/security/tool/VaultCommand.java b/tool/src/main/java/org/wildfly/security/tool/VaultCommand.java
index 3b3b2b2ee86..3637c9ba667 100644
--- a/tool/src/main/java/org/wildfly/security/tool/VaultCommand.java
+++ b/tool/src/main/java/org/wildfly/security/tool/VaultCommand.java
@@ -54,7 +54,6 @@
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
-import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.wildfly.security.auth.SupportLevel;
@@ -67,6 +66,10 @@
import org.wildfly.security.credential.store.impl.KeyStoreCredentialStore;
import org.wildfly.security.credential.store.impl.VaultCredentialStore;
import org.wildfly.security.password.interfaces.ClearPassword;
+import org.wildfly.security.tool.help.DescriptionSection;
+import org.wildfly.security.tool.help.HelpCommand;
+import org.wildfly.security.tool.help.OptionsSection;
+import org.wildfly.security.tool.help.UsageSection;
import org.wildfly.security.util.PasswordBasedEncryptionUtil;
/**
@@ -85,6 +88,7 @@ public class VaultCommand extends Command {
public static final String VAULT_COMMAND = "vault";
public static final String FAIL_IF_EXIST_PARAM = "fail-if-exist";
+ public static final String MASK_PREFIX = "MASK-";
// convert options
public static final String KEYSTORE_PASSWORD_PARAM = "keystore-password";
@@ -244,13 +248,15 @@ private void checkInvalidOptions(String... invalidOptions) throws Exception {
*/
@Override
public void help() {
- HelpFormatter help = new HelpFormatter();
- help.setWidth(WIDTH);
- help.printHelp(ElytronToolMessages.msg.cmdHelp(getToolCommand(), VAULT_COMMAND),
- ElytronToolMessages.msg.cmdVaultHelpHeader().concat(ElytronToolMessages.msg.cmdLineActionsHelpHeader()),
- options,
- "",
- true);
+ OptionsSection optionsSection = new OptionsSection(ElytronToolMessages.msg.cmdLineActionsHelpHeader(), options);
+ UsageSection usageSection = new UsageSection(VAULT_COMMAND, null);
+ DescriptionSection descriptionSection = new DescriptionSection(ElytronToolMessages.msg.cmdVaultHelpHeader());
+ HelpCommand helpCommand = HelpCommand.HelpCommandBuilder.builder()
+ .description(descriptionSection)
+ .usage(usageSection)
+ .options(optionsSection)
+ .build();
+ helpCommand.printHelp();
}
private String convertedStoreName(String encryptionDirectory, Map implProps) {
@@ -391,7 +397,7 @@ private List parseDescriptorFile(String descriptorFileLocation) thro
}
private CredentialSourceProtectionParameter getCredentialStoreProtectionParameter(final String vaultPassword, final String salt, final int iterationCount) throws GeneralSecurityException {
- char[] password = vaultPassword.startsWith("MASK-") ? decodeMaskedPassword(vaultPassword.substring("MASK-".length()), salt, iterationCount)
+ char[] password = vaultPassword.startsWith(MASK_PREFIX) ? decodeMaskedPassword(vaultPassword.substring(MASK_PREFIX.length()), salt, iterationCount)
: vaultPassword.toCharArray();
return new CredentialStore.CredentialSourceProtectionParameter(
IdentityCredentials.NONE.withCredential(
@@ -399,7 +405,7 @@ private CredentialSourceProtectionParameter getCredentialStoreProtectionParamete
}
private CredentialSourceProtectionParameter getVaultCredentialStoreProtectionParameter(final String keyStoreURL, final String vaultPassword, final String salt, final int iterationCount, final String secretKeyAlias) throws GeneralSecurityException, IOException {
- char[] password = vaultPassword.startsWith("MASK-") ? decodeMaskedPassword(vaultPassword.substring("MASK-".length()), salt, iterationCount)
+ char[] password = vaultPassword.startsWith(MASK_PREFIX) ? decodeMaskedPassword(vaultPassword.substring(MASK_PREFIX.length()), salt, iterationCount)
: vaultPassword.toCharArray();
final KeyStore keyStore = KeyStore.getInstance(defaultKeyStoreType);
try (FileInputStream in = new FileInputStream(new File(keyStoreURL))) {
@@ -445,7 +451,7 @@ private void printSummary (String keystorePassword, String salt, int iterationCo
if (keystorePassword != null) {
password = keystorePassword;
if (salt != null && iterationCount > -1) {
- password = keystorePassword.startsWith("MASK-") ? keystorePassword + ";" + salt + ";" + String.valueOf(iterationCount)
+ password = keystorePassword.startsWith(MASK_PREFIX) ? keystorePassword + ";" + salt + ";" + iterationCount
: MaskCommand.computeMasked(keystorePassword, salt, iterationCount);
}
}
diff --git a/tool/src/main/java/org/wildfly/security/tool/help/CommandsSection.java b/tool/src/main/java/org/wildfly/security/tool/help/CommandsSection.java
new file mode 100644
index 00000000000..46fa6086c9c
--- /dev/null
+++ b/tool/src/main/java/org/wildfly/security/tool/help/CommandsSection.java
@@ -0,0 +1,114 @@
+/*
+ * 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.tool.help;
+
+import java.util.Map;
+
+/**
+ * Command section of Elytron help command
+ *
+ * @author Petr Beran
+ */
+public class CommandsSection extends HelpSection {
+
+ private final String sectionTitle;
+ private final Map sectionContent;
+
+ public CommandsSection(Map commands) {
+ this.sectionTitle = "Commands";
+ this.sectionContent = commands;
+ }
+
+ @Override
+ public void printHelp() {
+ formatAndPrintTitle(sectionTitle);
+ if (sectionContent != null) {
+
+ // Find the longest commandName
+ // This is needed to make sure that all descriptions start at the same index
+ int longestCommand = 0;
+ for (String command : sectionContent.keySet()) {
+ if (command.length() > longestCommand) {
+ longestCommand = command.length();
+ }
+ }
+ for (Map.Entry command : sectionContent.entrySet()) {
+ formatAndPrintCommand(command.getKey(), command.getValue(), longestCommand);
+ }
+ }
+ }
+
+ /**
+ * Formats and prints command and it's respective description
+ *
+ * @param commandName Command's name
+ * @param commandDescription Command's description
+ * @param longestCommand Length of the longest commands. Ensures that all descriptions start at the same column
+ */
+ protected void formatAndPrintCommand(String commandName, final CharSequence commandDescription, final int longestCommand) {
+ CharSequence descriptionText = commandDescription;
+ final StringBuilder stringBuilder = new StringBuilder();
+
+ int minCommandAndDescGap = 4; // Gap between the longest commandName and its commandDescription
+ int commandDescriptionStartingIndex = longestCommand + minCommandAndDescGap + leftPadding; // Starting index of all commandDescriptions in the map
+ int commandDescriptionLength = lineWidth - commandDescriptionStartingIndex;
+
+ appendGap(stringBuilder, leftPadding);
+ stringBuilder.append(commandName);
+
+ // Append a gap so that all commandDescriptions in the map start at the same index
+ int realGap = commandDescriptionStartingIndex - leftPadding - commandName.length();
+ appendGap(stringBuilder, realGap);
+
+ // If the commandDescription fits one line, simply append it
+ if (descriptionText.length() <= commandDescriptionLength) {
+ stringBuilder.append(descriptionText);
+ stringBuilder.append(System.lineSeparator());
+ }
+ else {
+ int lineIndex = checkForWhitespaceIndex(descriptionText, commandDescriptionLength);
+
+ // Append the commandDescription that fits on a single line and remove it from the descriptionText
+ stringBuilder.append(descriptionText.subSequence(0,lineIndex));
+ descriptionText = descriptionText.subSequence(lineIndex+1, descriptionText.length());
+ stringBuilder.append(System.lineSeparator());
+
+ // Appends commandDescriptions from second row onward
+ while(0 < descriptionText.length()) {
+
+ // Append a gap so that all commandDescriptions in the map start at the same index
+ appendGap(stringBuilder, commandDescriptionStartingIndex);
+
+ // If the commandDescription fits one line, simply append it and end the while loop
+ if (descriptionText.length() <= commandDescriptionLength) {
+ stringBuilder.append(descriptionText);
+ stringBuilder.append(System.lineSeparator());
+ break;
+ }
+
+ lineIndex = checkForWhitespaceIndex(descriptionText, commandDescriptionLength);
+
+ // Append the commandDescription that fits on a single line and remove it from the descriptionText
+ stringBuilder.append(descriptionText.subSequence(0,lineIndex));
+ descriptionText = descriptionText.subSequence(lineIndex+1, descriptionText.length());
+ stringBuilder.append(System.lineSeparator());
+ }
+ }
+ printText(stringBuilder.toString());
+ }
+}
diff --git a/tool/src/main/java/org/wildfly/security/tool/help/DescriptionSection.java b/tool/src/main/java/org/wildfly/security/tool/help/DescriptionSection.java
new file mode 100644
index 00000000000..8721093e15f
--- /dev/null
+++ b/tool/src/main/java/org/wildfly/security/tool/help/DescriptionSection.java
@@ -0,0 +1,43 @@
+/*
+ * 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.tool.help;
+
+/**
+ * Description section of Elytron help command
+ *
+ * @author Petr Beran
+ */
+public class DescriptionSection extends HelpSection {
+
+ private final String sectionTitle;
+ private final String sectionContent;
+
+
+ public DescriptionSection(String sectionContent) {
+ this.sectionTitle = "Description";
+ this.sectionContent = sectionContent;
+ }
+
+ @Override
+ public void printHelp() {
+ formatAndPrintTitle(sectionTitle);
+ if (sectionContent != null) {
+ formatAndPrintSectionContext(sectionContent);
+ }
+ }
+}
diff --git a/tool/src/main/java/org/wildfly/security/tool/help/HelpCommand.java b/tool/src/main/java/org/wildfly/security/tool/help/HelpCommand.java
new file mode 100644
index 00000000000..342cd2457af
--- /dev/null
+++ b/tool/src/main/java/org/wildfly/security/tool/help/HelpCommand.java
@@ -0,0 +1,115 @@
+/*
+ * 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.tool.help;
+
+import org.aesh.readline.tty.terminal.TerminalConnection;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Provides help for the Elytron Tool command
+ *
+ * @author Petr Beran
+ */
+public class HelpCommand {
+
+ private final List helpSections;
+ private static TerminalConnection terminalConnection;
+
+ private HelpCommand(HelpCommandBuilder helpCommandBuilder) {
+ this.helpSections = helpCommandBuilder.helpSections;
+ }
+
+ /**
+ * Displays all sections for the help command
+ */
+ public void printHelp() {
+ if (terminalConnection == null) {
+ try {
+ terminalConnection = new TerminalConnection(Charset.defaultCharset(), System.in, System.out);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ terminalConnection.write(System.lineSeparator());
+ for (HelpSection helpSection : helpSections){
+ helpSection.printHelp();
+ }
+ terminalConnection.close();
+ }
+
+ public static TerminalConnection getTerminal() {
+ return terminalConnection;
+ }
+
+ public static class HelpCommandBuilder {
+
+ private UsageSection usageSection;
+ private DescriptionSection descriptionSection;
+ private CommandsSection commandsSection;
+ private OptionsSection optionsSection;
+
+ private final List helpSections = new ArrayList<>();
+
+ private HelpCommandBuilder() {}
+
+ public static HelpCommandBuilder builder() {
+ return new HelpCommandBuilder();
+ }
+
+ public HelpCommandBuilder usage(UsageSection usageSection) {
+ this.usageSection = usageSection;
+ return this;
+ }
+
+ public HelpCommandBuilder description(DescriptionSection descriptionSection) {
+ this.descriptionSection = descriptionSection;
+ return this;
+ }
+
+ public HelpCommandBuilder commands(CommandsSection commandsSection) {
+ this.commandsSection = commandsSection;
+ return this;
+ }
+
+ public HelpCommandBuilder options(OptionsSection optionsSection) {
+ this.optionsSection = optionsSection;
+ return this;
+ }
+
+ public HelpCommand build() {
+ // Ensures that all sections are in specific order and the order cannot be tampered with
+ if (descriptionSection != null) {
+ helpSections.add(descriptionSection);
+ }
+ if (usageSection != null) {
+ helpSections.add(usageSection);
+ }
+ if (commandsSection != null) {
+ helpSections.add(commandsSection);
+ }
+ if (optionsSection != null) {
+ helpSections.add(optionsSection);
+ }
+ return new HelpCommand(this);
+ }
+ }
+}
diff --git a/tool/src/main/java/org/wildfly/security/tool/help/HelpSection.java b/tool/src/main/java/org/wildfly/security/tool/help/HelpSection.java
new file mode 100644
index 00000000000..a6f5a6727f4
--- /dev/null
+++ b/tool/src/main/java/org/wildfly/security/tool/help/HelpSection.java
@@ -0,0 +1,122 @@
+/*
+ * 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.tool.help;
+
+import org.aesh.readline.terminal.formatting.CharacterType;
+import org.aesh.readline.terminal.formatting.Color;
+import org.aesh.readline.terminal.formatting.TerminalColor;
+import org.aesh.readline.terminal.formatting.TerminalString;
+import org.aesh.readline.terminal.formatting.TerminalTextStyle;
+import org.aesh.readline.tty.terminal.TerminalConnection;
+
+/**
+ * General section of Elytron help command
+ * All Elytron help command sections should extend this one
+ *
+ * @author Petr Beran
+ */
+public abstract class HelpSection {
+
+ final int leftPadding = 4;
+ final int lineWidth = 120;
+ final int textWidth = lineWidth - leftPadding;
+
+ /**
+ * Displays help of specific section
+ */
+ public abstract void printHelp();
+
+ /**
+ * Formats and prints a simple block of text
+ * For printing commands see {@link CommandsSection}
+ *
+ * @param text Text to print
+ */
+ protected void formatAndPrintSectionContext(final CharSequence text) {
+ final StringBuilder stringBuilder = new StringBuilder();
+ CharSequence contentText = text;
+ while(0 < contentText.length()) {
+ appendGap(stringBuilder, leftPadding);
+ // If the text fits one line, simply append it and end the while loop
+ if (contentText.length() <= textWidth) {
+ stringBuilder.append(contentText);
+ stringBuilder.append(System.lineSeparator());
+ break;
+ }
+ int lineIndex = checkForWhitespaceIndex(contentText, textWidth);
+ // Append the text that fits on a single line and remove it from the contentText
+ stringBuilder.append(contentText.subSequence(0,lineIndex));
+ contentText = contentText.subSequence(lineIndex+1, contentText.length());
+ stringBuilder.append(System.lineSeparator());
+ }
+ printText(stringBuilder.toString());
+ }
+
+ /**
+ * Formats and prints headers across all sections
+ *
+ * @param sectionTitle Title to format and print
+ */
+ protected void formatAndPrintTitle(String sectionTitle) {
+ String titleText = new TerminalString(sectionTitle.toUpperCase(),
+ new TerminalColor(Color.CYAN, Color.DEFAULT, Color.Intensity.BRIGHT),
+ new TerminalTextStyle(CharacterType.BOLD)).toString();
+ printText(titleText);
+ System.out.print(System.lineSeparator());
+ }
+
+ /**
+ * Finds the index of text that still fits on a single line and is a whitespace.
+ * We don't want to break words at the end of the line
+ *
+ * @param text Text to iterate
+ * @param maxWidth Max width of the line, start of the iteration
+ * @return Last whitespace index before the end of the line
+ */
+ protected int checkForWhitespaceIndex(CharSequence text, int maxWidth) {
+ int lastWhitespaceIndex = maxWidth;
+ while (0 <= lastWhitespaceIndex && !Character.isWhitespace(text.charAt(lastWhitespaceIndex))) {
+ lastWhitespaceIndex--;
+ }
+ return lastWhitespaceIndex;
+ }
+
+ /**
+ * Appends a gap of certain width
+ *
+ * @param text Text to which the gap should be appended
+ * @param gapWidth Width of the gap
+ */
+ protected void appendGap(StringBuilder text, int gapWidth) {
+ for (int i = 0; i < gapWidth; i++){
+ text.append(' ');
+ }
+ }
+
+ /**
+ * Prints the text via system terminal and adds a line separator at the end. Doesn't add any formatting.
+ *
+ * @param text Text to print. Leave {@code null} for just a line separator.
+ */
+ void printText(String text) {
+ TerminalConnection terminalConnection = HelpCommand.getTerminal();
+ if (text != null) {
+ terminalConnection.write(text);
+ }
+ }
+}
diff --git a/tool/src/main/java/org/wildfly/security/tool/help/OptionsSection.java b/tool/src/main/java/org/wildfly/security/tool/help/OptionsSection.java
new file mode 100644
index 00000000000..d82f0186619
--- /dev/null
+++ b/tool/src/main/java/org/wildfly/security/tool/help/OptionsSection.java
@@ -0,0 +1,55 @@
+/*
+ * 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.tool.help;
+
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Options;
+import org.wildfly.security.tool.ElytronToolMessages;
+
+/**
+ * Options section of Elytron help tool
+ * @author Petr Beran
+ */
+public class OptionsSection extends HelpSection {
+
+ private final String sectionTitle;
+ private final String sectionHeader;
+ private final Options sectionContent;
+
+ public OptionsSection(String sectionHeader, Options options) {
+ this.sectionTitle = "Options";
+ this.sectionHeader = sectionHeader;
+ this.sectionContent = options;
+ }
+
+ @Override
+ public void printHelp() {
+ formatAndPrintTitle(sectionTitle);
+ if (sectionHeader != null) {
+ formatAndPrintSectionContext(sectionHeader);
+ }
+ if (sectionContent != null) {
+ HelpFormatter help = new HelpFormatter();
+ help.setSyntaxPrefix("");
+ help.setLeftPadding(4);
+ help.setWidth(120);
+ help.printHelp(ElytronToolMessages.msg.cmdHelp("", ""), sectionContent);
+ printText(null);
+ }
+ }
+}
diff --git a/tool/src/main/java/org/wildfly/security/tool/help/UsageSection.java b/tool/src/main/java/org/wildfly/security/tool/help/UsageSection.java
new file mode 100644
index 00000000000..01c235c318d
--- /dev/null
+++ b/tool/src/main/java/org/wildfly/security/tool/help/UsageSection.java
@@ -0,0 +1,56 @@
+/*
+ * 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.tool.help;
+
+/**
+ * Usage section of Elytron help command
+ *
+ * @author Petr Beran
+ */
+public class UsageSection extends HelpSection {
+
+ private final String sectionTitle;
+ private final String sectionContent;
+
+ public UsageSection(String command, String option) {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append("./elytron-tool.sh");
+
+ if (command != null) {
+ stringBuilder.append(" " + command);
+ }
+ else {
+ stringBuilder.append(" [command]");
+ }
+
+ if (option != null) {
+ stringBuilder.append(" " + option);
+ }
+ else {
+ stringBuilder.append(" [options]");
+ }
+ this.sectionTitle = "Usage";
+ this.sectionContent = stringBuilder.toString();
+ }
+
+ @Override
+ public void printHelp() {
+ formatAndPrintTitle(sectionTitle);
+ formatAndPrintSectionContext(sectionContent);
+ }
+}
diff --git a/tool/src/test/java/org/wildfly/security/tool/FileSystemEncryptRealmCommandTest.java b/tool/src/test/java/org/wildfly/security/tool/FileSystemEncryptRealmCommandTest.java
index 6f698df14ff..32ff3c29f2d 100644
--- a/tool/src/test/java/org/wildfly/security/tool/FileSystemEncryptRealmCommandTest.java
+++ b/tool/src/test/java/org/wildfly/security/tool/FileSystemEncryptRealmCommandTest.java
@@ -23,13 +23,17 @@
import java.io.File;
import java.io.FileNotFoundException;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.SecretKey;
import org.apache.commons.cli.MissingArgumentException;
+import org.junit.AfterClass;
import org.junit.Test;
import org.wildfly.security.auth.principal.NamePrincipal;
import org.wildfly.security.auth.realm.FileSystemSecurityRealm;
@@ -229,6 +233,18 @@ public void testSingleUserAndVerify() throws Exception {
existingIdentity.dispose();
}
+ @AfterClass
+ public static void cleanup() throws Exception {
+ //cleanup after testBulkWithoutNames test
+ Path bulkWithoutNamesFolderPath = Paths.get(RELATIVE_BASE_DIR + "fs-encrypted-realms/bulk-encryption-conversion-desc-without-names");
+ if (bulkWithoutNamesFolderPath.toFile().exists()) {
+ Files.walk(bulkWithoutNamesFolderPath)
+ .sorted(Comparator.reverseOrder())
+ .map(Path::toFile)
+ .forEach(File::delete);
+ }
+ }
+
private boolean fileExists(String path) {
File tempFile = new File(path);
return tempFile.exists();
diff --git a/tool/src/test/java/org/wildfly/security/tool/MaskCommandTest.java b/tool/src/test/java/org/wildfly/security/tool/MaskCommandTest.java
index 3ce57f7b912..36327402573 100644
--- a/tool/src/test/java/org/wildfly/security/tool/MaskCommandTest.java
+++ b/tool/src/test/java/org/wildfly/security/tool/MaskCommandTest.java
@@ -17,6 +17,7 @@
*/
package org.wildfly.security.tool;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.wildfly.security.tool.Params.LINE_SEPARATOR;
@@ -51,7 +52,7 @@ public void maskCompatibilityCheck() throws Exception {
String retVal = executeCommandAndCheckStatusAndGetOutput(args);
String retValNoNewLine = retVal.substring(0, retVal.indexOf(LINE_SEPARATOR));
- assertTrue("output has to be the as pre-generated one", ("MASK-" + pbGenerated + ";" + "ASDF1234" + ";" + 123).equals(retValNoNewLine));
+ assertEquals("output has to be the as pre-generated one", "MASK-" + pbGenerated + ";" + "ASDF1234" + ";" + 123, retValNoNewLine);
}
@Test
@@ -64,7 +65,7 @@ public void testMissingSaltAndIteration() {
String[] retValLines = retVal.split(LINE_SEPARATOR);
assertTrue("Message about invalid salt parameter must be present", retValLines[0].contains("Invalid \"salt\" parameter. Generated value"));
- assertTrue("Message about invalid iteration parameter must be present", ("Invalid \"iteration\" parameter. Default value \"" + defaultIteration + "\" will be used.").equals(retValLines[1]));
+ assertEquals("Message about invalid iteration parameter must be present", "Invalid \"iteration\" parameter. Default value \"" + defaultIteration + "\" will be used.", retValLines[1]);
assertTrue("Message about invalid salt parameter must be present", retValLines[2].contains("MASK-"));
}
@@ -80,8 +81,9 @@ public void testMissingIteration() {
String retVal = executeCommandAndCheckStatusAndGetOutput(args);
String[] retValLines = retVal.split(LINE_SEPARATOR);
- assertTrue("Message about invalid iteration parameter must be present", ("Invalid \"iteration\" parameter. Default value \"" + defaultIteration + "\" will be used.").equals(retValLines[0]));
- assertTrue("Output has to be the as pre-generated one", ("MASK-" + pregenerated + ";" + salt + ";" + defaultIteration).equals(retValLines[1]));
+ assertEquals("Message about invalid iteration parameter must be present", "Invalid \"iteration\" parameter. Default value \"" + defaultIteration + "\" will be used.",
+ retValLines[0]);
+ assertEquals("Output has to be the as pre-generated one", "MASK-" + pregenerated + ";" + salt + ";" + defaultIteration, retValLines[1]);
}
@Test
@@ -124,12 +126,13 @@ public void testIterationAsStringValue() {
String[] args = { "--secret", secret, "--salt", salt, "--iteration", "abcd" };
+
String retVal = executeCommandAndCheckStatusAndGetOutput(args);
String[] retValLines = retVal.split(LINE_SEPARATOR);
- assertTrue("IllegalArgumentException must be present", ("java.lang.IllegalArgumentException: ELYTOOL00007: Invalid \"iteration\" value. Must be an integer between 1 and 2147483647, inclusive").equals(retValLines[0]));
- assertTrue("Message about invalid iteration parameter must be present", ("Invalid \"iteration\" parameter. Default value \"" + defaultIteration + "\" will be used.").equals(retValLines[1]));
- assertTrue("Output has to be the as pre-generated one", ("MASK-" + pregenerated + ";" + salt + ";" + defaultIteration).equals(retValLines[2]));
+ assertEquals("IllegalArgumentException must be present", "java.lang.IllegalArgumentException: ELYTOOL00007: Invalid \"iteration\" value. Must be an integer between 1 and 2147483647, inclusive", retValLines[0]);
+ assertEquals("Message about invalid iteration parameter must be present", "Invalid \"iteration\" parameter. Default value \"" + defaultIteration + "\" will be used.", retValLines[1]);
+ assertEquals("Output has to be the as pre-generated one", "MASK-" + pregenerated + ";" + salt + ";" + defaultIteration, retValLines[2]);
}
@Test
@@ -144,9 +147,9 @@ public void testIterationAsLongMax() {
String retVal = executeCommandAndCheckStatusAndGetOutput(args);
String[] retValLines = retVal.split(LINE_SEPARATOR);
- assertTrue("IllegalArgumentException must be present", ("java.lang.IllegalArgumentException: ELYTOOL00007: Invalid \"iteration\" value. Must be an integer between 1 and 2147483647, inclusive").equals(retValLines[0]));
- assertTrue("Message about invalid iteration parameter must be present", ("Invalid \"iteration\" parameter. Default value \"" + defaultIteration + "\" will be used.").equals(retValLines[1]));
- assertTrue("Output has to be the as pre-generated one", ("MASK-" + pregenerated + ";" + salt + ";" + defaultIteration).equals(retValLines[2]));
+ assertEquals("IllegalArgumentException must be present", "java.lang.IllegalArgumentException: ELYTOOL00007: Invalid \"iteration\" value. Must be an integer between 1 and 2147483647, inclusive", retValLines[0]);
+ assertEquals("Message about invalid iteration parameter must be present", "Invalid \"iteration\" parameter. Default value \"" + defaultIteration + "\" will be used.", retValLines[1]);
+ assertEquals("Output has to be the as pre-generated one", "MASK-" + pregenerated + ";" + salt + ";" + defaultIteration, retValLines[2]);
}
@Test
@@ -161,9 +164,9 @@ public void testIterationAsNegativeValue() {
String retVal = executeCommandAndCheckStatusAndGetOutput(args);
String[] retValLines = retVal.split(LINE_SEPARATOR);
- assertTrue("IllegalArgumentException must be present", ("java.lang.IllegalArgumentException: ELYTOOL00007: Invalid \"iteration\" value. Must be an integer between 1 and 2147483647, inclusive").equals(retValLines[0]));
- assertTrue("Message about invalid iteration parameter must be present", ("Invalid \"iteration\" parameter. Default value \"" + defaultIteration + "\" will be used.").equals(retValLines[1]));
- assertTrue("Output has to be the as pre-generated one", ("MASK-" + pregenerated + ";" + salt + ";" + defaultIteration).equals(retValLines[2]));
+ assertEquals("IllegalArgumentException must be present", "java.lang.IllegalArgumentException: ELYTOOL00007: Invalid \"iteration\" value. Must be an integer between 1 and 2147483647, inclusive",retValLines[0]);
+ assertEquals("Message about invalid iteration parameter must be present", "Invalid \"iteration\" parameter. Default value \"" + defaultIteration + "\" will be used.",retValLines[1]);
+ assertEquals("Output has to be the as pre-generated one", "MASK-" + pregenerated + ";" + salt + ";" + defaultIteration, retValLines[2]);
}
@Test
@@ -186,4 +189,18 @@ public void testDuplicateOptions() {
Assert.assertTrue(output.contains("Option \"secret\" specified more than once. Only the first occurrence will be used."));
Assert.assertFalse(output.contains("Option \"iteration\" specified more than once. Only the first occurrence will be used"));
}
+
+ @Test
+ public void testDecryptMasked() throws Exception {
+ final String originalSecret = "super_secret";
+ final String salt = "ASDF1234";
+ final int iterationCount = 123;
+ final String preGeneratedMaskedPassword = "MASK-088WUKotOwu7VOS8xRj.Rr;ASDF1234;123";
+
+ char[] decryptedSecret = MaskCommand.decryptMasked(preGeneratedMaskedPassword);
+
+ Assert.assertNotNull("Decrypted secret should not be null", decryptedSecret);
+ Assert.assertEquals("Decrypted secret should match the original secret", originalSecret, new String(decryptedSecret));
+ }
+
}
diff --git a/tool/src/test/java/org/wildfly/security/tool/VaultCommandTest.java b/tool/src/test/java/org/wildfly/security/tool/VaultCommandTest.java
index e53cc183589..c456ca44661 100644
--- a/tool/src/test/java/org/wildfly/security/tool/VaultCommandTest.java
+++ b/tool/src/test/java/org/wildfly/security/tool/VaultCommandTest.java
@@ -384,7 +384,7 @@ public void bulkConversionBasicTest() throws Exception {
// conversion
String output = executeCommandAndCheckStatusAndGetOutput(args);
String[] parts = output.split("converted to credential store");
- Assert.assertTrue("Three credential stores has to be created", parts.length == 4);
+ Assert.assertEquals("Three credential stores has to be created", 4, parts.length);
Assert.assertTrue("Check file names must pass", output.indexOf("vault-v1/vault-jceks.keystore") > 0 && output.indexOf("vault-v1-more/vault-jceks.keystore") > 0);
// check result
@@ -508,7 +508,7 @@ private void executeVaultCommandWithParams(String[] args, boolean shouldPass, St
String message = "Execution of vault command with arguments {" + String.join(" ", args) + "} should" +
(shouldPass? " succeeded ": " failed ") + "but it" + (shouldPass? " failed": " succeeded");
- Assert.assertTrue(message, passed == shouldPass);
+ Assert.assertEquals(message, shouldPass, passed);
if (expectedOutput != null) {
Assert.assertTrue("Command output should contain \"" + expectedOutput + "\"", output.contains(expectedOutput));
diff --git a/tool/src/test/resources/bulk-encryption-conversion-desc-without-names b/tool/src/test/resources/bulk-encryption-conversion-desc-without-names
index d28bded00a9..d51ee5e5b79 100644
--- a/tool/src/test/resources/bulk-encryption-conversion-desc-without-names
+++ b/tool/src/test/resources/bulk-encryption-conversion-desc-without-names
@@ -1,22 +1,22 @@
input-location:target/test-classes/filesystem-encrypt/fs-unencrypted-realms/multiple-credential-types
-output-location:target/test-classes/filesystem-encrypt/fs-encrypted-realms
+output-location:target/test-classes/filesystem-encrypt/fs-encrypted-realms/bulk-encryption-conversion-desc-without-names
credential-store:target/test-classes/filesystem-encrypt/mycredstore.cs
create:true
levels:1
input-location:target/test-classes/filesystem-encrypt/fs-unencrypted-realms/level-4
-output-location:target/test-classes/filesystem-encrypt/fs-encrypted-realms
+output-location:target/test-classes/filesystem-encrypt/fs-encrypted-realms/bulk-encryption-conversion-desc-without-names
credential-store:target/test-classes/filesystem-encrypt/mycredstore.cs
create:true
levels:4
input-location:target/test-classes/filesystem-encrypt/fs-unencrypted-realms/fsRealmCharset
-output-location:target/test-classes/filesystem-encrypt/fs-encrypted-realms
+output-location:target/test-classes/filesystem-encrypt/fs-encrypted-realms/bulk-encryption-conversion-desc-without-names
credential-store:target/test-classes/filesystem-encrypt/mycredstore.cs
create:true
input-location:target/test-classes/filesystem-encrypt/fs-unencrypted-realms/hashencoding
-output-location:target/test-classes/filesystem-encrypt/fs-encrypted-realms
+output-location:target/test-classes/filesystem-encrypt/fs-encrypted-realms/bulk-encryption-conversion-desc-without-names
hash-encoding:hex
credential-store:target/test-classes/filesystem-encrypt/mycredstore.cs
-create:true
+create:true
\ No newline at end of file
diff --git a/util/pom.xml b/util/pom.xml
index 9706c48891a..4ee89f9b23a 100644
--- a/util/pom.xml
+++ b/util/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
4.0.0
diff --git a/wildfly-elytron/pom.xml b/wildfly-elytron/pom.xml
index 1c21d79333d..2bf06d9be43 100644
--- a/wildfly-elytron/pom.xml
+++ b/wildfly-elytron/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
4.0.0
@@ -293,6 +293,10 @@
org.wildfly.security
wildfly-elytron-digest
+
+ org.wildfly.security
+ wildfly-elytron-dynamic-ssl
+
org.wildfly.security
wildfly-elytron-encryption
@@ -546,14 +550,14 @@
com.github.siom79.japicmp
japicmp-maven-plugin
- 0.13.0
+ 0.20.0
false
org.wildfly.security
wildfly-elytron
- 2.2.6.Final
+ 2.6.0.Final
jar
@@ -633,6 +637,11 @@
wildfly-elytron-digest
${project.version}
+
+ org.wildfly.security
+ wildfly-elytron-dynamic-ssl
+ ${project.version}
+
org.wildfly.security
wildfly-elytron-http
@@ -945,6 +954,8 @@
org.wildfly.security.auth.client.ElytronMessages_$logger
+ org.wildfly.security.auth.util.ElytronMessages_$logger
+ org.wildfly.security.auth.util.GSSCredentialSecurityFactory
true
diff --git a/x500/base/pom.xml b/x500/base/pom.xml
index 35cae1ea990..740db174f50 100644
--- a/x500/base/pom.xml
+++ b/x500/base/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/x500/base/src/main/java/org/wildfly/security/x500/X500.java b/x500/base/src/main/java/org/wildfly/security/x500/X500.java
index 1c1e6ddafd4..0ab9ed83401 100644
--- a/x500/base/src/main/java/org/wildfly/security/x500/X500.java
+++ b/x500/base/src/main/java/org/wildfly/security/x500/X500.java
@@ -246,12 +246,10 @@ private static boolean createX509CertificateChain(final X509Certificate firstCer
return false;
}
for (X509Certificate issuerCertificate : issuerCertificates) {
- if (issuedBy(firstCertificate, issuerCertificate)) {
+ if (issuedBy(firstCertificate, issuerCertificate) && createX509CertificateChain(issuerCertificate, certificateChain, certificatesMap)) {
// recurse
- if (createX509CertificateChain(issuerCertificate, certificateChain, certificatesMap)) {
- certificateChain.add(firstCertificate);
- return true;
- }
+ certificateChain.add(firstCertificate);
+ return true;
}
}
return false;
diff --git a/x500/base/src/main/java/org/wildfly/security/x500/util/X500PrincipalUtil.java b/x500/base/src/main/java/org/wildfly/security/x500/util/X500PrincipalUtil.java
index 391d79a1c81..2ae2012821f 100644
--- a/x500/base/src/main/java/org/wildfly/security/x500/util/X500PrincipalUtil.java
+++ b/x500/base/src/main/java/org/wildfly/security/x500/util/X500PrincipalUtil.java
@@ -168,9 +168,7 @@ public static String[] getAttributeValues(X500Principal principal, String oid, b
}
} else {
// The attribute values will be in reverse order
- for (int i = 0; i < len; i++) {
- result[i] = strings[i];
- }
+ System.arraycopy(strings, 0, result, 0, len);
}
return result;
}
diff --git a/x500/cert/acme/pom.xml b/x500/cert/acme/pom.xml
index 7627c921410..c2040a3d5ed 100644
--- a/x500/cert/acme/pom.xml
+++ b/x500/cert/acme/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../../pom.xml
@@ -105,7 +105,7 @@
test
- org.glassfish
+ org.eclipse.parsson
jakarta.json
test
diff --git a/x500/cert/acme/src/main/java/org/wildfly/security/x500/cert/acme/AcmeClientSpi.java b/x500/cert/acme/src/main/java/org/wildfly/security/x500/cert/acme/AcmeClientSpi.java
index 3003ae0d4c4..452480eb7a9 100644
--- a/x500/cert/acme/src/main/java/org/wildfly/security/x500/cert/acme/AcmeClientSpi.java
+++ b/x500/cert/acme/src/main/java/org/wildfly/security/x500/cert/acme/AcmeClientSpi.java
@@ -921,10 +921,8 @@ private static long getRetryAfter(HttpURLConnection connection, boolean useDefau
}
}
- if (retryAfterMilli == -1) {
- if (useDefaultIfHeaderNotPresent) {
- retryAfterMilli = DEFAULT_RETRY_AFTER_MILLI;
- }
+ if (retryAfterMilli == -1 && useDefaultIfHeaderNotPresent) {
+ retryAfterMilli = DEFAULT_RETRY_AFTER_MILLI;
}
return retryAfterMilli;
}
diff --git a/x500/cert/base/pom.xml b/x500/cert/base/pom.xml
index 73a0de714fc..6af3f479612 100644
--- a/x500/cert/base/pom.xml
+++ b/x500/cert/base/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../../pom.xml
diff --git a/x500/cert/util/pom.xml b/x500/cert/util/pom.xml
index a9a9a560873..5a548aa9725 100644
--- a/x500/cert/util/pom.xml
+++ b/x500/cert/util/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../../pom.xml
@@ -55,7 +55,7 @@
org.jboss.logging
jboss-logging-processor
provided
-
+
diff --git a/x500/deprecated/pom.xml b/x500/deprecated/pom.xml
index a8883a3dc6b..27075a20280 100644
--- a/x500/deprecated/pom.xml
+++ b/x500/deprecated/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml
diff --git a/x500/deprecated/src/main/java/org/wildfly/security/x500/X500AttributePrincipalDecoder.java b/x500/deprecated/src/main/java/org/wildfly/security/x500/X500AttributePrincipalDecoder.java
index e311f0f2a21..13169db15af 100644
--- a/x500/deprecated/src/main/java/org/wildfly/security/x500/X500AttributePrincipalDecoder.java
+++ b/x500/deprecated/src/main/java/org/wildfly/security/x500/X500AttributePrincipalDecoder.java
@@ -31,7 +31,7 @@
* A principal decoder which decodes an attribute from an X.500 principal.
*
* @author David M. Lloyd
- * @deprecated Use {@link org.wildfly.security.x500.principal.X500AttributePrincipalDecoder} instead
+ * @deprecated Use {@link org.wildfly.security.x500.principal.X500AttributePrincipalDecoder org.wildfly.security.x500.principal.X500AttributePrincipalDecoder} instead
*/
@Deprecated
public final class X500AttributePrincipalDecoder implements PrincipalDecoder {
diff --git a/x500/principal/pom.xml b/x500/principal/pom.xml
index 59aae9d9af8..341c8a24c22 100644
--- a/x500/principal/pom.xml
+++ b/x500/principal/pom.xml
@@ -24,7 +24,7 @@
org.wildfly.security
wildfly-elytron-parent
- 2.2.7.CR1-SNAPSHOT
+ 2.6.1.CR1-SNAPSHOT
../../pom.xml