From 25082efd6761cbe18ce4d49d56859db082779a17 Mon Sep 17 00:00:00 2001 From: rmartinc Date: Wed, 2 Feb 2022 10:52:02 +0100 Subject: [PATCH] [WFSSL-92] Allow using EC certificates with OpenSSL --- .../wildfly/openssl/OpenSSLContextSPI.java | 46 +++++--- .../openssl/BasicOpenSSLSocketECTest.java | 108 ++++++++++++++++++ .../org/wildfly/openssl/SSLTestUtils.java | 106 ++++++----------- java/src/test/resources/client-ec.keystore | Bin 0 -> 526 bytes java/src/test/resources/client-ec.truststore | Bin 0 -> 417 bytes java/src/test/resources/server-ec.keystore | Bin 0 -> 552 bytes java/src/test/resources/server-ec.truststore | Bin 0 -> 391 bytes 7 files changed, 173 insertions(+), 87 deletions(-) create mode 100644 java/src/test/java/org/wildfly/openssl/BasicOpenSSLSocketECTest.java create mode 100644 java/src/test/resources/client-ec.keystore create mode 100644 java/src/test/resources/client-ec.truststore create mode 100644 java/src/test/resources/server-ec.keystore create mode 100644 java/src/test/resources/server-ec.truststore diff --git a/java/src/main/java/org/wildfly/openssl/OpenSSLContextSPI.java b/java/src/main/java/org/wildfly/openssl/OpenSSLContextSPI.java index d956e04c..fbfdcfb1 100644 --- a/java/src/main/java/org/wildfly/openssl/OpenSSLContextSPI.java +++ b/java/src/main/java/org/wildfly/openssl/OpenSSLContextSPI.java @@ -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; @@ -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) { @@ -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; } } diff --git a/java/src/test/java/org/wildfly/openssl/BasicOpenSSLSocketECTest.java b/java/src/test/java/org/wildfly/openssl/BasicOpenSSLSocketECTest.java new file mode 100644 index 00000000..9203f9aa --- /dev/null +++ b/java/src/test/java/org/wildfly/openssl/BasicOpenSSLSocketECTest.java @@ -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; + +/** + *

Test class that uses TLSv1.2 and TLSv1.3 to connect a client and server + * using openssl engine and the EC certificates.

+ * + * @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 sessionID = new AtomicReference<>(); + final AtomicReference 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); + } +} diff --git a/java/src/test/java/org/wildfly/openssl/SSLTestUtils.java b/java/src/test/java/org/wildfly/openssl/SSLTestUtils.java index e1635eb4..f779dabe 100644 --- a/java/src/test/java/org/wildfly/openssl/SSLTestUtils.java +++ b/java/src/test/java/org/wildfly/openssl/SSLTestUtils.java @@ -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 { @@ -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 { diff --git a/java/src/test/resources/client-ec.keystore b/java/src/test/resources/client-ec.keystore new file mode 100644 index 0000000000000000000000000000000000000000..ec4a27fb95b5b146ced2e7e5eec1ace715aa28af GIT binary patch literal 526 zcmezO_TO6u1_mY|W&~rlU{Cfk=I5hzw~P;0=)#-+{1$ik?_B*@6f%D|F+ z(amFjdHvoK>z1}|nYNj6-QuaYR;-Z!JB`&y$?<@!;MQO#>2lZ6blWDkuNOpj&eJoxvPvM6efEiMjwkmts%Yq) z-pD33F}6S3<2~3}tPy&q29`iqc^EV?x)?ORT)@o4$i&1F_0DUb0S_C8R-4B;TNY*} zRt5txLlFZZHs(+kW?}A-)Z!8aXRwb96vTOr3=Iu{(A>n>z$i+B-^jq+(7@8j0xE!0 zXQY8J8#~wrCPt`L%#7^JP7Ex*ynZ~UTT~C9|9486|C5pb>c|_8U#kT?+%89_-urBS0v#*M$0EieGB?%u zht~r|i-=`sR384@@G|qR#WPgjF?%o=xH2iEh+Y1-pzrsKrfHXF&fj)r(e#@KRHQt9 z9!dUrC+d`oHxrX0!-b&xzyE*v@rHZ(g7>`B0=P^x7RGE{^XBhDx8{6X2QRbki(2Jc U&wmL?t=RgTPQsYO6;O?F^uJp*fmo~eN)keg)C#25#}j0>2V7@3$@ zJbxGH8t|}jXtjBqvt?msVr4K8F%&WoU}FwtVHW1h$xlwq$;dA*F;EcaH8M0b077#U zLsQcz34S93b3+45BMYbi4tb3ZX3 zdC7f9?V_<(VCF?eLCab5++ySo-%Wq>;YS|3>?tqv#?!x=8kjoocy~HIi&8C(yLYQ~ zv8{o%fh^FWvV1IJEFx`Nmw${kbXe}NNGe(^@cf&NuN3_Zq(IVwEW!qYO$fiVv(y7a z0@c^do(u+VOo|K>K0C1%NV>0v99AMe_~*=-ErjTj}{;+iNW+Q~2fPOyz&-`%|{L zJh(HvdDi26Lnf89L+WY0(>sJiH9Xaw`(N$T+w#uxh3WoBLjISNSFU1>N~yA_`II^_ zP|W|MOYUYpu(Mbr^h^ybfv!q2Xkv^5V#Wo`OpHuSES|p$bPageIJDY4&e^gsGqExl zh!_eP2(U4SvM>vC=Hw?Q=49j-ml!CB^BNf%8UUfWiJ_@!lmx$#fw`f9rI7_x0EfOv z17S9Hu<1;UP-~bO*_oXfSUPGH-hSx$vR!4%!MUH9kG$kQq;}C*D=_mSqoC!id2TUs zhwrAp`S2r;UG|ihdE@C{O$|()cf32Do<*sa#@)Nsy4cph+CUcQP+2|}F&2@wt;;{g z8aga@SR@rK7I^;6##f4d22vntK^9>H!6t-X+F9y>A%W^^W={qKHzq}f37?%<582pm zdN^mN^BTw9D+Ks_m<)5oOSD_OkL6x8Y^xU5jU^zGL=aFmPp3ND;gIZ$aPh z7fsVH&z!&Q%A)Bv52#3a{5+EU^G?($7jGseMTQGO_kaKY^5YHn@&)gCrv-4CXe^A` ly5`N_g>KFHwhmrq7Ys$k<)&ZtWO7-VoLwB(W^?i0djR>ngtGtu literal 0 HcmV?d00001