Skip to content

Commit

Permalink
[WFSSL-92] Allow using EC certificates with OpenSSL
Browse files Browse the repository at this point in the history
  • Loading branch information
rmartinc committed Feb 2, 2022
1 parent ad7f770 commit d77a7c8
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 87 deletions.
46 changes: 32 additions & 14 deletions java/src/main/java/org/wildfly/openssl/OpenSSLContextSPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,33 @@ public abstract class OpenSSLContextSPI extends SSLContextSpi {

public static final int DEFAULT_SESSION_CACHE_SIZE = 1000;

private static final String BEGIN_RSA_CERT = "-----BEGIN RSA PRIVATE KEY-----\n";

private static final String END_RSA_CERT = "\n-----END RSA PRIVATE KEY-----";
private static enum KeyAlgorithm {
RSA(SSL.SSL_AIDX_RSA),
EC(SSL.SSL_AIDX_ECC),
DSA(SSL.SSL_AIDX_DSA);

private final int idx;
private final String beginStanza;
private final String endStanza;

KeyAlgorithm(int idx) {
this.idx = idx;
this.beginStanza = String.format("-----BEGIN %s PRIVATE KEY-----\n", name());
this.endStanza = String.format("\n-----END %s PRIVATE KEY-----", name());
}

private static final String BEGIN_DSA_CERT = "-----BEGIN DSA PRIVATE KEY-----\n";
public String getBeginStanza() {
return beginStanza;
}

private static final String END_DSA_CERT = "\n-----END DSA PRIVATE KEY-----";
public String getEndStanza() {
return endStanza;
}

private static final String[] ALGORITHMS = {"RSA", "DSA"};
public int getAlgorithmIndex() {
return idx;
}
}

private OpenSSLServerSessionContext serverSessionContext;
private OpenSSLClientSessionContext clientSessionContext;
Expand Down Expand Up @@ -179,10 +197,9 @@ private synchronized void init(KeyManager[] kms, TrustManager[] tms) throws KeyM
// Load Server key and certificate
X509KeyManager keyManager = chooseKeyManager(kms);
if (keyManager != null) {
for (String algorithm : ALGORITHMS) {
for (KeyAlgorithm algorithm : KeyAlgorithm.values()) {

boolean rsa = algorithm.equals("RSA");
final String[] aliases = keyManager.getServerAliases(algorithm, null);
final String[] aliases = keyManager.getServerAliases(algorithm.name(), null);
if (aliases != null && aliases.length != 0) {
for(String alias: aliases) {

Expand All @@ -192,22 +209,23 @@ private synchronized void init(KeyManager[] kms, TrustManager[] tms) throws KeyM
continue;
}
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Using alias " + alias + " for " + algorithm);
LOG.log(Level.FINE, "Using alias {0} for {1}", new Object[]{alias, algorithm});
}
StringBuilder sb = new StringBuilder(rsa ? BEGIN_RSA_CERT : BEGIN_DSA_CERT);
byte[] encodedPrivateKey = key.getEncoded();
if (encodedPrivateKey == null) {
throw new KeyManagementException(Messages.MESSAGES.unableToObtainPrivateKey());
}
sb.append(Base64.getMimeEncoder(64, new byte[]{'\n'}).encodeToString(encodedPrivateKey));
sb.append(rsa ? END_RSA_CERT : END_DSA_CERT);
String keyString = algorithm.getBeginStanza()
+ Base64.getMimeEncoder(64, new byte[]{'\n'}).encodeToString(encodedPrivateKey)
+ algorithm.getEndStanza();

byte[][] encodedIntermediaries = new byte[certificateChain.length - 1][];
for(int i = 1; i < certificateChain.length; ++i) {
encodedIntermediaries[i - 1] = certificateChain[i].getEncoded();
}
X509Certificate certificate = certificateChain[0];
SSL.getInstance().setCertificate(ctx, certificate.getEncoded(), encodedIntermediaries, sb.toString().getBytes(StandardCharsets.US_ASCII), rsa ? SSL.SSL_AIDX_RSA : SSL.SSL_AIDX_DSA);
SSL.getInstance().setCertificate(ctx, certificate.getEncoded(), encodedIntermediaries,
keyString.getBytes(StandardCharsets.US_ASCII), algorithm.getAlgorithmIndex());
break;
}
}
Expand Down
108 changes: 108 additions & 0 deletions java/src/test/java/org/wildfly/openssl/BasicOpenSSLSocketECTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* JBoss, Home of Professional Open Source.
*
* Copyright 2022 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.openssl;

import java.io.IOException;
import java.net.ServerSocket;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.concurrent.atomic.AtomicReference;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSocket;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Test;
import static org.wildfly.openssl.OpenSSLEngine.isTLS13Supported;
import static org.wildfly.openssl.SSL.SSL_PROTO_TLSv1_2;
import static org.wildfly.openssl.SSL.SSL_PROTO_TLSv1_3;

/**
* <p>Test class that uses TLSv1.2 and TLSv1.3 to connect a client and server
* using openssl engine and the EC certificates.</p>
*
* @author rmartinc
*/
public class BasicOpenSSLSocketECTest extends AbstractOpenSSLTest {

public void testECCertificates(String protocol, boolean opensslClient, boolean opensslServer) throws IOException, NoSuchAlgorithmException, InterruptedException {

try (ServerSocket serverSocket = SSLTestUtils.createServerSocket()) {
final AtomicReference<byte[]> sessionID = new AtomicReference<>();
final AtomicReference<SSLEngine> engineRef = new AtomicReference<>();

Thread acceptThread = new Thread(new EchoRunnable(serverSocket,
SSLTestUtils.createECSSLContext(opensslServer? "openssl." + protocol : protocol), sessionID,
engine -> {
engine.setNeedClientAuth(true);
engineRef.set(engine);
return engine;
}));
acceptThread.start();
final SSLContext sslContext = SSLTestUtils.createClientECSSLContext(opensslClient? "openssl." + protocol : protocol);
try (SSLSocket socket = (SSLSocket) sslContext.getSocketFactory().createSocket()) {
socket.setReuseAddress(true);
socket.connect(SSLTestUtils.createSocketAddress());
socket.getOutputStream().write("hello world".getBytes(StandardCharsets.US_ASCII));
socket.getOutputStream().flush();
byte[] data = new byte[100];
int read = socket.getInputStream().read(data);

Assert.assertEquals("hello world", new String(data, 0, read));
if (!SSL_PROTO_TLSv1_3.equals(protocol)) {
Assert.assertArrayEquals(socket.getSession().getId(), sessionID.get());
}
Assert.assertEquals(protocol, socket.getSession().getProtocol());
Assert.assertNotNull(socket.getSession().getCipherSuite());

Assert.assertNotNull(socket.getSession().getPeerCertificates());
Assert.assertTrue(socket.getSession().getPeerCertificates().length > 0);
Assert.assertTrue(socket.getSession().getPeerCertificates()[0] instanceof X509Certificate);
Assert.assertEquals("CN=localhost", ((X509Certificate) socket.getSession().getPeerCertificates()[0]).getSubjectDN().getName());
Assert.assertEquals("EC", ((X509Certificate) socket.getSession().getPeerCertificates()[0]).getPublicKey().getAlgorithm());

Assert.assertNotNull(engineRef.get().getSession().getPeerCertificates());
Assert.assertTrue(engineRef.get().getSession().getPeerCertificates().length > 0);
Assert.assertTrue(engineRef.get().getSession().getPeerCertificates()[0] instanceof X509Certificate);
Assert.assertEquals("CN=Test Client", ((X509Certificate) engineRef.get().getSession().getPeerCertificates()[0]).getSubjectDN().getName());
Assert.assertEquals("EC", ((X509Certificate) engineRef.get().getSession().getPeerCertificates()[0]).getPublicKey().getAlgorithm());
socket.getSession().invalidate();
}
serverSocket.close();
acceptThread.join();
}
}

@Test
public void testTLSv12() throws IOException, NoSuchAlgorithmException, InterruptedException {
testECCertificates(SSL_PROTO_TLSv1_2, true, true);
testECCertificates(SSL_PROTO_TLSv1_2, true, false);
testECCertificates(SSL_PROTO_TLSv1_2, false, true);
}

@Test
public void testTLSv13() throws IOException, NoSuchAlgorithmException, InterruptedException {
Assume.assumeTrue(isTLS13Supported());
testECCertificates(SSL_PROTO_TLSv1_3, true, true);
testECCertificates(SSL_PROTO_TLSv1_3, true, false);
testECCertificates(SSL_PROTO_TLSv1_3, false, true);
}
}
106 changes: 33 additions & 73 deletions java/src/test/java/org/wildfly/openssl/SSLTestUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,23 +58,27 @@ private static KeyStore loadKeyStore(final String name) throws IOException {
}
}

static SSLContext createSSLContext(String provider) throws IOException {
KeyManager[] keyManagers;
try {
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(loadKeyStore("server.keystore"), "password".toCharArray());
keyManagers = keyManagerFactory.getKeyManagers();
} catch (NoSuchAlgorithmException | UnrecoverableKeyException | KeyStoreException e) {
throw new RuntimeException("Unable to initialise KeyManager[]", e);
private static SSLContext createSSLContext(String provider, String keystore, String truststore) throws IOException {
KeyManager[] keyManagers = null;
if (keystore != null) {
try {
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(loadKeyStore(keystore), "password".toCharArray());
keyManagers = keyManagerFactory.getKeyManagers();
} catch (NoSuchAlgorithmException | UnrecoverableKeyException | KeyStoreException e) {
throw new RuntimeException("Unable to initialise KeyManager[]", e);
}
}

TrustManager[] trustManagers = null;
try {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(loadKeyStore("server.truststore"));
trustManagers = trustManagerFactory.getTrustManagers();
} catch (NoSuchAlgorithmException | KeyStoreException e) {
throw new RuntimeException("Unable to initialise TrustManager[]", e);
if (truststore != null) {
try {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(loadKeyStore(truststore));
trustManagers = trustManagerFactory.getTrustManagers();
} catch (NoSuchAlgorithmException | KeyStoreException e) {
throw new RuntimeException("Unable to initialise TrustManager[]", e);
}
}

try {
Expand All @@ -87,72 +91,28 @@ static SSLContext createSSLContext(String provider) throws IOException {
}
}

static SSLContext createClientSSLContext(String provider) throws IOException {
KeyManager[] keyManagers;
try {
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(loadKeyStore("client.keystore"), "password".toCharArray());
keyManagers = keyManagerFactory.getKeyManagers();
} catch (NoSuchAlgorithmException | UnrecoverableKeyException | KeyStoreException e) {
throw new RuntimeException("Unable to initialise KeyManager[]", e);
}

TrustManager[] trustManagers = null;
try {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(loadKeyStore("client.truststore"));
trustManagers = trustManagerFactory.getTrustManagers();
} catch (NoSuchAlgorithmException | KeyStoreException e) {
throw new RuntimeException("Unable to initialise TrustManager[]", e);
}
static SSLContext createSSLContext(String provider) throws IOException {
return createSSLContext(provider, "server.keystore", "server.truststore");
}

try {
final SSLContext context = SSLContext.getInstance(provider);
context.init(keyManagers, trustManagers, new SecureRandom());
return context;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Unable to create and initialise the SSLContext", e);
}
static SSLContext createClientSSLContext(String provider) throws IOException {
return createSSLContext(provider, "client.keystore", "client.truststore");
}

static SSLContext createDSASSLContext(String provider) throws IOException {
KeyManager[] keyManagers;
try {
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(loadKeyStore("server-dsa.keystore"), "password".toCharArray());
keyManagers = keyManagerFactory.getKeyManagers();
} catch (NoSuchAlgorithmException | UnrecoverableKeyException | KeyStoreException e) {
throw new RuntimeException("Unable to initialise KeyManager[]", e);
}

try {
final SSLContext context = SSLContext.getInstance(provider);
context.init(keyManagers, null, new SecureRandom());
return context;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Unable to create and initialise the SSLContext", e);
}
return createSSLContext(provider, "server-dsa.keystore", null);
}

static SSLContext createClientDSASSLContext(String provider) throws IOException {
TrustManager[] trustManagers = null;
try {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(loadKeyStore("client-dsa.truststore"));
trustManagers = trustManagerFactory.getTrustManagers();
} catch (NoSuchAlgorithmException | KeyStoreException e) {
throw new RuntimeException("Unable to initialise TrustManager[]", e);
}
return createSSLContext(provider, null, "client-dsa.truststore");
}

try {
final SSLContext context = SSLContext.getInstance(provider);
context.init(null, trustManagers, new SecureRandom());
return context;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Unable to create and initialise the SSLContext", e);
}
static SSLContext createECSSLContext(String provider) throws IOException {
return createSSLContext(provider, "server-ec.keystore", "server-ec.truststore");
}

static SSLContext createClientECSSLContext(String provider) throws IOException {
return createSSLContext(provider, "client-ec.keystore", "client-ec.truststore");
}

public static byte[] readData(InputStream in) throws IOException {
Expand Down
Binary file added java/src/test/resources/client-ec.keystore
Binary file not shown.
Binary file added java/src/test/resources/client-ec.truststore
Binary file not shown.
Binary file added java/src/test/resources/server-ec.keystore
Binary file not shown.
Binary file added java/src/test/resources/server-ec.truststore
Binary file not shown.

0 comments on commit d77a7c8

Please sign in to comment.