diff --git a/ssl/src/main/java/org/wildfly/security/ssl/Protocol.java b/ssl/src/main/java/org/wildfly/security/ssl/Protocol.java
index 021c325fc59..186b0893426 100644
--- a/ssl/src/main/java/org/wildfly/security/ssl/Protocol.java
+++ b/ssl/src/main/java/org/wildfly/security/ssl/Protocol.java
@@ -53,6 +53,10 @@ public enum Protocol {
* The TLS version 1.3 protocol.
*/
TLSv1_3 ("TLSV1.3"),
+ /**
+ * The SSL version 2 hello protocol
+ */
+ SSLv2Hello("SSLV2HELLO")
;
static final int fullSize = values().length;
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
new file mode 100644
index 00000000000..5549f7aa0ce
--- /dev/null
+++ b/tests/base/src/test/java/org/wildfly/security/ssl/SSLv2HelloAuthenticationTest.java
@@ -0,0 +1,348 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2020 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.wildfly.security.ssl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.net.InetAddress;
+
+import java.net.URI;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PublicKey;
+import java.security.PrivateKey;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.ExecutorService;
+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 javax.security.auth.x500.X500Principal;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.wildfly.security.WildFlyElytronProvider;
+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.permission.PermissionVerifier;
+import org.wildfly.security.x500.principal.X500AttributePrincipalDecoder;
+import org.wildfly.security.x500.cert.BasicConstraintsExtension;
+import org.wildfly.security.x500.cert.SelfSignedX509CertificateAndSigningKey;
+import org.wildfly.security.x500.cert.X509CertificateBuilder;
+
+/**
+ * Simple test case to test two-way SSL using SSLv2Hello.
+ *
+ * @author Sonia Zaldana
+ */
+public class SSLv2HelloAuthenticationTest {
+
+ private static final boolean IS_IBM = System.getProperty("java.vendor").contains("IBM");
+ private static final char[] PASSWORD = "Elytron".toCharArray();
+ private static final String CA_JKS_LOCATION = "./target/test-classes/ca/jks";
+ private static File ladybirdFile = null;
+ private static File scarabFile = null;
+ private static File beetlesFile = null;
+ private static File trustFile = null;
+ private static File workingDirCA = null;
+
+ @BeforeClass
+ public static void setUp() throws Exception{
+ workingDirCA = new File(CA_JKS_LOCATION);
+ if (!workingDirCA.exists()) {
+ workingDirCA.mkdirs();
+ }
+
+ ladybirdFile = new File(workingDirCA,"ladybird.keystore");
+ scarabFile = new File(workingDirCA,"scarab.keystore");
+ beetlesFile = new File(workingDirCA,"beetles.keystore");
+ trustFile = new File(workingDirCA,"ca.truststore");
+
+ createKeyStores(ladybirdFile, scarabFile, beetlesFile, trustFile);
+ }
+
+
+ @AfterClass
+ public static void cleanUp(){
+ ladybirdFile.delete();
+ ladybirdFile = null;
+ scarabFile.delete();
+ scarabFile = null;
+ beetlesFile.delete();
+ beetlesFile = null;
+ trustFile.delete();
+ trustFile = null;
+ workingDirCA.delete();
+ workingDirCA = null;
+ }
+
+ @Test
+ public void testTwoWaySSLv2Hello() throws Exception {
+
+ SecurityRealm securityRealm = new KeyStoreBackedSecurityRealm(loadKeyStore("/ca/jks/beetles.keystore"));
+
+ SecurityDomain securityDomain = SecurityDomain.builder()
+ .addRealm("KeystoreRealm", securityRealm)
+ .build()
+ .setDefaultRealmName("KeystoreRealm")
+ .setPrincipalDecoder(new X500AttributePrincipalDecoder("2.5.4.3", 1))
+ .setPreRealmRewriter((String s) -> s.toLowerCase(Locale.ENGLISH))
+ .setPermissionMapper((permissionMappable, roles) -> PermissionVerifier.ALL)
+ .build();
+
+ List list = new ArrayList<>();
+ list.add(Protocol.forName("SSLv2Hello"));
+ list.add(Protocol.forName("TLSv1"));
+
+ SSLContext serverContext = new SSLContextBuilder()
+ .setSecurityDomain(securityDomain)
+ .setKeyManager(getKeyManager("/ca/jks/scarab.keystore"))
+ .setTrustManager(getCATrustManager())
+ .setNeedClientAuth(true)
+ .setProtocolSelector(ProtocolSelector.empty().add(EnumSet.copyOf(list)))
+ .build().create();
+
+ SecurityIdentity identity = performConnectionTest(serverContext, "protocol://test-two-way-sslv2hello.org", "wildfly-ssl-test-config-v1_6.xml");
+ assertNotNull(identity);
+ assertEquals("Principal Name", "ladybird", identity.getPrincipal().getName());
+ }
+
+ private SecurityIdentity performConnectionTest(SSLContext serverContext, String clientUri, String clientConfigFileName) throws Exception {
+ System.setProperty("wildfly.config.url", SSLAuthenticationTest.class.getResource(clientConfigFileName).toExternalForm());
+ AccessController.doPrivileged((PrivilegedAction) () -> Security.insertProviderAt(new WildFlyElytronProvider(), 1));
+
+ AuthenticationContext context = AuthenticationContext.getContextManager().get();
+ AuthenticationContextConfigurationClient contextConfigurationClient = AccessController.doPrivileged(AuthenticationContextConfigurationClient.ACTION);
+ SSLContext clientContext = contextConfigurationClient.getSSLContext(URI.create(clientUri), context);
+
+ SSLServerSocketFactory sslServerSocketFactory = serverContext.getServerSocketFactory();
+
+ SSLServerSocket sslServerSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(1111, 10, InetAddress.getLoopbackAddress());
+
+ ExecutorService executorService = Executors.newSingleThreadExecutor();
+ Future socketFuture = executorService.submit(() -> {
+ try {
+ System.out.println("About to connect client");
+ SSLSocket sslSocket = (SSLSocket) clientContext.getSocketFactory().createSocket(InetAddress.getLoopbackAddress(), 1111);
+ sslSocket.getSession();
+
+ return sslSocket;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ } finally {
+ System.out.println("Client connected");
+ }
+ });
+
+ SSLSocket serverSocket = (SSLSocket) sslServerSocket.accept();
+ SSLSession serverSession = serverSocket.getSession();
+ SSLSocket clientSocket = socketFuture.get();
+ SSLSession clientSession = clientSocket.getSession();
+
+ try {
+ // The negotiated protocol must be TLSv1, as SSLv2Hello is only used to negotiate a safe protocol
+ assertEquals("TLSv1", serverSession.getProtocol());
+ assertEquals("TLSv1", clientSession.getProtocol());
+ return (SecurityIdentity) serverSession.getValue(SSLUtils.SSL_SESSION_IDENTITY_KEY);
+ } finally {
+ safeClose(serverSocket);
+ safeClose(clientSocket);
+ safeClose(sslServerSocket);
+ }
+ }
+
+ /**
+ * 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(IS_IBM ? "IbmX509" : "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(IS_IBM ? "IbmX509" : "SunX509");
+ trustManagerFactory.init(loadKeyStore("/ca/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() throws Exception{
+ KeyStore ks = KeyStore.getInstance("JKS");
+ ks.load(null,null);
+ return ks;
+ }
+
+ 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 static void createTemporaryKeyStoreFile(KeyStore keyStore, File outputFile, char[] password) throws Exception {
+ try (FileOutputStream fos = new FileOutputStream(outputFile)){
+ keyStore.store(fos, password);
+ }
+ }
+
+ private static void createKeyStores(File ladybirdFile, File scarabFile, File beetlesFile, File trustFile) throws Exception {
+ KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+ Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
+
+ X500Principal issuerDN = new X500Principal("CN=Elytron CA, ST=Elytron, C=UK, EMAILADDRESS=elytron@wildfly.org, O=Root Certificate Authority");
+ X500Principal intermediateIssuerDN = new X500Principal("CN=Elytron ICA, ST=Elytron, C=UK, O=Intermediate Certificate Authority");
+ X500Principal ladybirdDN = new X500Principal("OU=Elytron, O=Elytron, C=UK, ST=Elytron, CN=Ladybird");
+ X500Principal scarabDN = new X500Principal("OU=Elytron, O=Elytron, C=UK, ST=Elytron, CN=Scarab");
+
+ KeyStore ladybirdKeyStore = loadKeyStore();
+ KeyStore scarabKeyStore = loadKeyStore();
+ KeyStore beetlesKeyStore = loadKeyStore();
+ KeyStore trustStore = loadKeyStore();
+
+ // Generates the issuer certificate and adds it to the keystores
+ SelfSignedX509CertificateAndSigningKey issuerSelfSignedX509CertificateAndSigningKey = SelfSignedX509CertificateAndSigningKey.builder()
+ .setDn(issuerDN)
+ .setKeyAlgorithmName("RSA")
+ .setSignatureAlgorithmName("SHA1withRSA")
+ .addExtension(false, "BasicConstraints", "CA:true,pathlen:2147483647")
+ .build();
+ X509Certificate issuerCertificate = issuerSelfSignedX509CertificateAndSigningKey.getSelfSignedCertificate();
+ ladybirdKeyStore.setCertificateEntry("ca", issuerCertificate);
+ scarabKeyStore.setCertificateEntry("ca", issuerCertificate);
+ trustStore.setCertificateEntry("mykey",issuerCertificate);
+
+ // Generates the intermediate issuer certificate
+ KeyPair intermediateIssuerKeys = keyPairGenerator.generateKeyPair();
+ PrivateKey intermediateIssuerSigningKey = intermediateIssuerKeys.getPrivate();
+ PublicKey intermediateIssuerPublicKey = intermediateIssuerKeys.getPublic();
+
+ X509Certificate intermediateIssuerCertificate = new X509CertificateBuilder()
+ .setIssuerDn(issuerDN)
+ .setSubjectDn(intermediateIssuerDN)
+ .setSignatureAlgorithmName("SHA1withRSA")
+ .setSigningKey(issuerSelfSignedX509CertificateAndSigningKey.getSigningKey())
+ .setPublicKey(intermediateIssuerPublicKey)
+ .setSerialNumber(new BigInteger("6"))
+ .addExtension(new BasicConstraintsExtension(false, true, 0))
+ .build();
+
+ // Generates certificate and keystore for Ladybird
+ KeyPair ladybirdKeys = keyPairGenerator.generateKeyPair();
+ PrivateKey ladybirdSigningKey = ladybirdKeys.getPrivate();
+ PublicKey ladybirdPublicKey = ladybirdKeys.getPublic();
+
+ X509Certificate ladybirdCertificate = new X509CertificateBuilder()
+ .setIssuerDn(issuerDN)
+ .setSubjectDn(ladybirdDN)
+ .setSignatureAlgorithmName("SHA1withRSA")
+ .setSigningKey(issuerSelfSignedX509CertificateAndSigningKey.getSigningKey())
+ .setPublicKey(ladybirdPublicKey)
+ .setSerialNumber(new BigInteger("3"))
+ .addExtension(new BasicConstraintsExtension(false, false, -1))
+ .build();
+ ladybirdKeyStore.setKeyEntry("ladybird", ladybirdSigningKey, PASSWORD, new X509Certificate[]{ladybirdCertificate,issuerCertificate});
+
+ // Generates certificate and keystore for Scarab
+ KeyPair scarabKeys = keyPairGenerator.generateKeyPair();
+ PrivateKey scarabSigningKey = scarabKeys.getPrivate();
+ PublicKey scarabPublicKey = scarabKeys.getPublic();
+
+ X509Certificate scarabCertificate = new X509CertificateBuilder()
+ .setIssuerDn(issuerDN)
+ .setSubjectDn(scarabDN)
+ .setSignatureAlgorithmName("SHA1withRSA")
+ .setSigningKey(issuerSelfSignedX509CertificateAndSigningKey.getSigningKey())
+ .setPublicKey(scarabPublicKey)
+ .setSerialNumber(new BigInteger("4"))
+ .addExtension(new BasicConstraintsExtension(false, false, -1))
+ .build();
+ scarabKeyStore.setKeyEntry("scarab", scarabSigningKey, PASSWORD, new X509Certificate[]{scarabCertificate,issuerCertificate});
+
+ // Adds trusted certs for beetles
+ beetlesKeyStore.setCertificateEntry("ladybird", ladybirdCertificate);
+ beetlesKeyStore.setCertificateEntry("scarab", scarabCertificate);
+
+ // Create the temporary files
+ createTemporaryKeyStoreFile(ladybirdKeyStore, ladybirdFile, PASSWORD);
+ createTemporaryKeyStoreFile(scarabKeyStore, scarabFile, PASSWORD);
+ createTemporaryKeyStoreFile(beetlesKeyStore, beetlesFile, PASSWORD);
+ createTemporaryKeyStoreFile(trustStore, trustFile, PASSWORD);
+ }
+
+ private void safeClose(Closeable closeable) {
+ try {
+ closeable.close();
+ } catch (Exception ignored) {}
+ }
+}
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/wildfly-ssl-test-config-v1_6.xml
new file mode 100644
index 00000000000..347423d35b5
--- /dev/null
+++ b/tests/base/src/test/resources/org/wildfly/security/ssl/wildfly-ssl-test-config-v1_6.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+