From 11210c7b324fe76fab28911c3b56e35d50b2b024 Mon Sep 17 00:00:00 2001 From: Johannes Alberti Date: Sun, 28 Feb 2021 09:26:59 -0800 Subject: [PATCH 1/4] server sni support --- .../wildfly/openssl/OpenSSLContextSPI.java | 154 ++++++++++++++++-- .../main/java/org/wildfly/openssl/SSL.java | 8 +- 2 files changed, 140 insertions(+), 22 deletions(-) diff --git a/java/src/main/java/org/wildfly/openssl/OpenSSLContextSPI.java b/java/src/main/java/org/wildfly/openssl/OpenSSLContextSPI.java index d956e04c..b5951b78 100644 --- a/java/src/main/java/org/wildfly/openssl/OpenSSLContextSPI.java +++ b/java/src/main/java/org/wildfly/openssl/OpenSSLContextSPI.java @@ -20,6 +20,21 @@ import static org.wildfly.openssl.OpenSSLEngine.isTLS13Supported; +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContextSpi; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLServerSocketFactory; +import javax.net.ssl.SSLSessionContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509KeyManager; +import javax.net.ssl.X509TrustManager; + import java.io.IOException; import java.net.InetAddress; import java.net.ServerSocket; @@ -34,23 +49,19 @@ import java.security.SecureRandom; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; +import java.security.cert.CertificateParsingException; import java.security.cert.X509Certificate; +import java.util.ArrayList; import java.util.Base64; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; -import javax.net.ssl.KeyManager; -import javax.net.ssl.SSLContextSpi; -import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLException; -import javax.net.ssl.SSLServerSocketFactory; -import javax.net.ssl.SSLSessionContext; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509KeyManager; -import javax.net.ssl.X509TrustManager; public abstract class OpenSSLContextSPI extends SSLContextSpi { @@ -135,10 +146,15 @@ public static String[] getAvailableCipherSuites() { OpenSSLContextSPI(final int value) throws SSLException { this.supportedCiphers = value; SSL.init(); + ctx = makeSSLContext(); + } + + private long makeSSLContext() throws RuntimeException { + final long sslCtx; try { // Create SSL Context try { - ctx = SSL.getInstance().makeSSLContext(value, SSL.SSL_MODE_COMBINED); + sslCtx = SSL.getInstance().makeSSLContext(this.supportedCiphers, SSL.SSL_MODE_COMBINED); } catch (Exception e) { // If the sslEngine is disabled on the AprLifecycleListener // there will be an Exception here but there is no way to check @@ -147,19 +163,19 @@ public static String[] getAvailableCipherSuites() { } try { //disable unsafe renegotiation - SSL.getInstance().clearSSLContextOptions(ctx, SSL.SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION); + SSL.getInstance().clearSSLContextOptions(sslCtx, SSL.SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION); } catch (UnsatisfiedLinkError e) { // Ignore } // Disable compression - SSL.getInstance().setSSLContextOptions(ctx, SSL.SSL_OP_NO_COMPRESSION); + SSL.getInstance().setSSLContextOptions(sslCtx, SSL.SSL_OP_NO_COMPRESSION); // Disable TLS Session Tickets (RFC4507) to protect perfect forward secrecy - SSL.getInstance().setSSLContextOptions(ctx, SSL.SSL_OP_NO_TICKET); + SSL.getInstance().setSSLContextOptions(sslCtx, SSL.SSL_OP_NO_TICKET); } catch (Exception e) { throw new RuntimeException(Messages.MESSAGES.failedToInitializeSslContext(), e); } - + return sslCtx; } /** @@ -175,17 +191,31 @@ private synchronized void init(KeyManager[] kms, TrustManager[] tms) throws KeyM return; } + // a single subject can have multiple certificates for different algorithms, as + // aliases are required to be unique, the subject is the next best thing to establish + // some form of grouping, as a single context can have multiple certificates + // for different algorithms + final Map subjectToSSLContextMap = new LinkedHashMap<>(); + + // this simple map is used later on during certificate selection in the SNICallback, + // as a single ssl ctx can have multiple certificate, and SNI uses a requested + // hostname to allow the server to choose the certificate, we flatten everything + final Map, Long> x509CertificateToSSLContextMap = new LinkedHashMap<>(); + try { // Load Server key and certificate X509KeyManager keyManager = chooseKeyManager(kms); if (keyManager != null) { for (String algorithm : ALGORITHMS) { + int counter = 0; + boolean rsa = algorithm.equals("RSA"); final String[] aliases = keyManager.getServerAliases(algorithm, null); if (aliases != null && aliases.length != 0) { for(String alias: aliases) { + counter++; X509Certificate[] certificateChain = keyManager.getCertificateChain(alias); PrivateKey key = keyManager.getPrivateKey(alias); if(key == null || certificateChain == null || key.getEncoded() == null) { @@ -207,12 +237,54 @@ private synchronized void init(KeyManager[] kms, TrustManager[] tms) throws KeyM 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); - break; + + // for a single subject multiple certificates with different algorithms can exist, if + // we already have a context for a specific subject, use it, otherwise generate a new context + // to be used with SNI + Long sslCtx = subjectToSSLContextMap.get(certificate.getSubjectX500Principal().getName()); + + // if no existing context could be found, and this is the first round, establish the + // "default" context + if (sslCtx == null && counter == 1) { + sslCtx = ctx; + subjectToSSLContextMap.put(certificate.getSubjectX500Principal().getName(), sslCtx); + } else { + sslCtx = makeSSLContext(); + subjectToSSLContextMap.put(certificate.getSubjectX500Principal().getName(), sslCtx); + } + + // set the certifcates to use for this context + SSL.getInstance().setCertificate(sslCtx, certificate.getEncoded(), encodedIntermediaries, sb.toString().getBytes(StandardCharsets.US_ASCII), rsa ? SSL.SSL_AIDX_RSA : SSL.SSL_AIDX_DSA); + x509CertificateToSSLContextMap.put(getHostnames(certificate), sslCtx); } } } } + + if (x509CertificateToSSLContextMap.size() > 1) { + SSL.registerDefault(ctx, new SSL.SNICallBack() { + + @Override + public long getSslContext(String sniHostName) { + if (sniHostName == null || sniHostName.isEmpty()) { + return ctx; + } + + final String lowerSniHostname = sniHostName.toLowerCase(); + + for (ArrayList hostnames: x509CertificateToSSLContextMap.keySet()) { + + // find a ssl ctx by hostname + if (hostnames.contains(lowerSniHostname)) { + return x509CertificateToSSLContextMap.get(hostnames); + } + } + + return ctx; + } + }); + } + /* // Support Client Certificates SSL.getInstance().setCACertificate(ctx, @@ -442,6 +514,52 @@ public void sessionRemoved(byte[] session) { serverSessionContext.remove(session); } + private ArrayList getHostnames(X509Certificate cert) { + if (cert == null) { + Collections.emptyList(); + } + + final ArrayList hostnames = new ArrayList<>(); + final String certSubjectDN = cert.getSubjectX500Principal().getName(); + + // extract all the valid "hostnames" from the SANs + try { + final Collection> sansList = cert.getSubjectAlternativeNames(); + if (sansList != null && sansList.size() > 0) { + for (List san : sansList) { + switch ((Integer) san.get(0)) { + case 2: // DNS + case 7: // IP + Object sanData = san.get(1); + if (sanData instanceof String) { + hostnames.add((String) sanData); + } + break; + default: + } + } + } + } catch (CertificateParsingException ex) { + final String msg = String.format("Unable to parse SANS of own certificate [%s].", certSubjectDN); + LOG.log(Level.WARNING, msg, ex); + } + + // extract the commonName from the subject + try { + final LdapName ldapName = new LdapName(certSubjectDN); + final Rdn commonNameRdn = ldapName.getRdns().stream() + .filter(item -> item.getType().equalsIgnoreCase("CN")) + .findFirst() + .orElse(null); + hostnames.add((String) commonNameRdn.getValue()); + } catch (InvalidNameException ex) { + final String msg = String.format("Unable to parse subject of own certificate [%s].", certSubjectDN); + LOG.log(Level.WARNING, msg, ex); + } + + return hostnames; + } + public static final class OpenSSLTLSContextSpi extends OpenSSLContextSPI { public OpenSSLTLSContextSpi() throws SSLException { diff --git a/java/src/main/java/org/wildfly/openssl/SSL.java b/java/src/main/java/org/wildfly/openssl/SSL.java index a8cd0c96..a2f5deba 100644 --- a/java/src/main/java/org/wildfly/openssl/SSL.java +++ b/java/src/main/java/org/wildfly/openssl/SSL.java @@ -1112,7 +1112,7 @@ protected abstract boolean setCertificate(long ctx, byte[] cert, static long sniCallBack(long currentCtx, String sniHostName) { SNICallBack sniCallBack = sniCallBacks.get(Long.valueOf(currentCtx)); if (sniCallBack == null) { - return 0; + return currentCtx; } return sniCallBack.getSslContext(sniHostName); } @@ -1140,7 +1140,7 @@ static long sniCallBack(long currentCtx, String sniHostName) { * defaultSSLContext to the correct OpenSSL * SSLContext */ - static void registerDefault(Long defaultSSLContext, + public static void registerDefault(Long defaultSSLContext, SNICallBack sniCallBack) { sniCallBacks.put(defaultSSLContext, sniCallBack); } @@ -1152,7 +1152,7 @@ static void registerDefault(Long defaultSSLContext, * @param defaultSSLContext The Java representation of a pointer to the * OpenSSL SSLContext that will no longer be used */ - static void unregisterDefault(Long defaultSSLContext) { + public static void unregisterDefault(Long defaultSSLContext) { sniCallBacks.remove(defaultSSLContext); } @@ -1168,7 +1168,7 @@ static void unregisterDefault(Long defaultSSLContext) { * select an OpenSSL SSLContext based on the host name requested by the * client. */ - interface SNICallBack { + public interface SNICallBack { /** * This callback is made during the TLS handshake when the client uses From 698a33dfd8ab07cec84bd56ead5dfa75bb1189d4 Mon Sep 17 00:00:00 2001 From: Johannes Alberti Date: Sun, 28 Feb 2021 15:35:14 -0800 Subject: [PATCH 2/4] wildcard certs support for sni match --- .../wildfly/openssl/OpenSSLContextSPI.java | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/java/src/main/java/org/wildfly/openssl/OpenSSLContextSPI.java b/java/src/main/java/org/wildfly/openssl/OpenSSLContextSPI.java index b5951b78..c93fec2f 100644 --- a/java/src/main/java/org/wildfly/openssl/OpenSSLContextSPI.java +++ b/java/src/main/java/org/wildfly/openssl/OpenSSLContextSPI.java @@ -272,15 +272,30 @@ public long getSslContext(String sniHostName) { final String lowerSniHostname = sniHostName.toLowerCase(); - for (ArrayList hostnames: x509CertificateToSSLContextMap.keySet()) { + String sniHostnameAsWildcard = null; + final int idx = lowerSniHostname.indexOf('.'); + if (idx > 0) { + sniHostnameAsWildcard = "*" + lowerSniHostname.substring(idx); + } + + long wildcardSSLContext = 0L; - // find a ssl ctx by hostname + for (ArrayList hostnames: x509CertificateToSSLContextMap.keySet()) { + // find a ssl ctx by hostname, return if its a perfect match if (hostnames.contains(lowerSniHostname)) { return x509CertificateToSSLContextMap.get(hostnames); } + + // check if context might be good with as a wildcard cert, but + // there might be another ctx with a better match, so don't + // return it yet, let's wait until we checked all ctx avail + if (sniHostnameAsWildcard != null && hostnames.contains(sniHostnameAsWildcard)) { + wildcardSSLContext = x509CertificateToSSLContextMap.get(hostnames); + } } - return ctx; + // if we have a ssl ctx with a matching wildcard cert, prefer it + return wildcardSSLContext != 0L ? wildcardSSLContext : ctx; } }); } From fff680e5eaa063aaf7948b33f0af20eb7c8664a6 Mon Sep 17 00:00:00 2001 From: Johannes Alberti Date: Sun, 28 Feb 2021 15:37:28 -0800 Subject: [PATCH 3/4] no need to make these public --- java/src/main/java/org/wildfly/openssl/SSL.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/java/src/main/java/org/wildfly/openssl/SSL.java b/java/src/main/java/org/wildfly/openssl/SSL.java index a2f5deba..cd7a84c0 100644 --- a/java/src/main/java/org/wildfly/openssl/SSL.java +++ b/java/src/main/java/org/wildfly/openssl/SSL.java @@ -1140,7 +1140,7 @@ static long sniCallBack(long currentCtx, String sniHostName) { * defaultSSLContext to the correct OpenSSL * SSLContext */ - public static void registerDefault(Long defaultSSLContext, + static void registerDefault(Long defaultSSLContext, SNICallBack sniCallBack) { sniCallBacks.put(defaultSSLContext, sniCallBack); } @@ -1152,7 +1152,7 @@ public static void registerDefault(Long defaultSSLContext, * @param defaultSSLContext The Java representation of a pointer to the * OpenSSL SSLContext that will no longer be used */ - public static void unregisterDefault(Long defaultSSLContext) { + static void unregisterDefault(Long defaultSSLContext) { sniCallBacks.remove(defaultSSLContext); } @@ -1168,7 +1168,7 @@ public static void unregisterDefault(Long defaultSSLContext) { * select an OpenSSL SSLContext based on the host name requested by the * client. */ - public interface SNICallBack { + interface SNICallBack { /** * This callback is made during the TLS handshake when the client uses From 306a4a85678cd543eecf4ff1c1157657cf47ea1c Mon Sep 17 00:00:00 2001 From: Johannes Alberti Date: Sat, 5 Feb 2022 08:34:44 -0800 Subject: [PATCH 4/4] sni support, fix jni reference leak --- .../wildfly/openssl/OpenSSLContextSPI.java | 148 ++++++++++-------- .../org/wildfly/openssl/OpenSSLEngine.java | 65 ++++---- .../OpenSSLEngineServerALPNCallback.java | 21 +++ 3 files changed, 132 insertions(+), 102 deletions(-) create mode 100644 java/src/main/java/org/wildfly/openssl/OpenSSLEngineServerALPNCallback.java diff --git a/java/src/main/java/org/wildfly/openssl/OpenSSLContextSPI.java b/java/src/main/java/org/wildfly/openssl/OpenSSLContextSPI.java index c93fec2f..a8f097f2 100644 --- a/java/src/main/java/org/wildfly/openssl/OpenSSLContextSPI.java +++ b/java/src/main/java/org/wildfly/openssl/OpenSSLContextSPI.java @@ -20,10 +20,9 @@ import static org.wildfly.openssl.OpenSSLEngine.isTLS13Supported; -import javax.naming.InvalidNameException; -import javax.naming.ldap.LdapName; -import javax.naming.ldap.Rdn; import javax.net.ssl.KeyManager; +import javax.net.ssl.SNIHostName; +import javax.net.ssl.SNIMatcher; import javax.net.ssl.SSLContextSpi; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLException; @@ -51,17 +50,18 @@ import java.security.cert.CertificateFactory; import java.security.cert.CertificateParsingException; import java.security.cert.X509Certificate; -import java.util.ArrayList; import java.util.Base64; import java.util.Collection; -import java.util.Collections; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.regex.Pattern; public abstract class OpenSSLContextSPI extends SSLContextSpi { @@ -151,6 +151,7 @@ public static String[] getAvailableCipherSuites() { private long makeSSLContext() throws RuntimeException { final long sslCtx; + try { // Create SSL Context try { @@ -200,7 +201,7 @@ private synchronized void init(KeyManager[] kms, TrustManager[] tms) throws KeyM // this simple map is used later on during certificate selection in the SNICallback, // as a single ssl ctx can have multiple certificate, and SNI uses a requested // hostname to allow the server to choose the certificate, we flatten everything - final Map, Long> x509CertificateToSSLContextMap = new LinkedHashMap<>(); + final Map x509CertificateToSSLContextMap = new LinkedHashMap<>(); try { // Load Server key and certificate @@ -245,17 +246,19 @@ private synchronized void init(KeyManager[] kms, TrustManager[] tms) throws KeyM // if no existing context could be found, and this is the first round, establish the // "default" context - if (sslCtx == null && counter == 1) { - sslCtx = ctx; - subjectToSSLContextMap.put(certificate.getSubjectX500Principal().getName(), sslCtx); - } else { - sslCtx = makeSSLContext(); + if (sslCtx == null) { + if (counter == 1) { + sslCtx = ctx; + } else { + sslCtx = makeSSLContext(); + } + subjectToSSLContextMap.put(certificate.getSubjectX500Principal().getName(), sslCtx); } // set the certifcates to use for this context SSL.getInstance().setCertificate(sslCtx, certificate.getEncoded(), encodedIntermediaries, sb.toString().getBytes(StandardCharsets.US_ASCII), rsa ? SSL.SSL_AIDX_RSA : SSL.SSL_AIDX_DSA); - x509CertificateToSSLContextMap.put(getHostnames(certificate), sslCtx); + x509CertificateToSSLContextMap.put(getHostnamesSNIMatcher(certificate), sslCtx); } } } @@ -270,27 +273,32 @@ public long getSslContext(String sniHostName) { return ctx; } - final String lowerSniHostname = sniHostName.toLowerCase(); + final String lowerSniHostname = sniHostName.toLowerCase(Locale.ENGLISH); + final SNIHostName lowerSniHostnameForMatcher = new SNIHostName(lowerSniHostname.getBytes(StandardCharsets.UTF_8)); String sniHostnameAsWildcard = null; + SNIHostName sniHostnameAsWildcardForMatcher = null; + final int idx = lowerSniHostname.indexOf('.'); + if (idx > 0) { sniHostnameAsWildcard = "*" + lowerSniHostname.substring(idx); + sniHostnameAsWildcardForMatcher = new SNIHostName(sniHostnameAsWildcard.getBytes(StandardCharsets.UTF_8)); } long wildcardSSLContext = 0L; - for (ArrayList hostnames: x509CertificateToSSLContextMap.keySet()) { + for (final SNIMatcher hostnameMatcher: x509CertificateToSSLContextMap.keySet()) { // find a ssl ctx by hostname, return if its a perfect match - if (hostnames.contains(lowerSniHostname)) { - return x509CertificateToSSLContextMap.get(hostnames); + if (hostnameMatcher.matches(lowerSniHostnameForMatcher)) { + return x509CertificateToSSLContextMap.get(hostnameMatcher); } // check if context might be good with as a wildcard cert, but // there might be another ctx with a better match, so don't // return it yet, let's wait until we checked all ctx avail - if (sniHostnameAsWildcard != null && hostnames.contains(sniHostnameAsWildcard)) { - wildcardSSLContext = x509CertificateToSSLContextMap.get(hostnames); + if (sniHostnameAsWildcard != null && hostnameMatcher.matches(sniHostnameAsWildcardForMatcher)) { + wildcardSSLContext = x509CertificateToSSLContextMap.get(hostnameMatcher); } } @@ -314,29 +322,15 @@ public long getSslContext(String sniHostName) { */ // Client certificate verification - SSL.getInstance().setSessionCacheSize(ctx, DEFAULT_SESSION_CACHE_SIZE); - final X509TrustManager manager = chooseTrustManager(tms); - if(manager != null) { - SSL.getInstance().setCertVerifyCallback(ctx, (ssl, chain, cipherNo, server) -> { - X509Certificate[] peerCerts = certificates(chain); - Cipher cipher = Cipher.valueOf(cipherNo); - String auth = cipher == null ? "RSA" : cipher.getAu().toString(); - try { - if(server) { - manager.checkClientTrusted(peerCerts, auth); - } else { - manager.checkServerTrusted(peerCerts, auth); - } - return true; - } catch (Exception e) { - if (LOG.isLoggable(Level.FINE)) { - LOG.log(Level.FINE, "Certificate verification failed", e); - } - } - return false; - }); - } + final Set sslContexts = new HashSet<>(x509CertificateToSSLContextMap.values()); + if (sslContexts.isEmpty()) { + configureSSLContext(tms, ctx); + } else { + for (long sslCtx : sslContexts) { + configureSSLContext(tms, sslCtx); + } + } serverSessionContext = new OpenSSLServerSessionContext(ctx); serverSessionContext.setSessionIdContext("test".getBytes(StandardCharsets.US_ASCII)); @@ -351,6 +345,31 @@ public long getSslContext(String sniHostName) { } } + private void configureSSLContext(final TrustManager[] tms, final long sslCtx) { + SSL.getInstance().setSessionCacheSize(sslCtx, DEFAULT_SESSION_CACHE_SIZE); + final X509TrustManager manager = chooseTrustManager(tms); + if(manager != null) { + SSL.getInstance().setCertVerifyCallback(sslCtx, (ssl, chain, cipherNo, server) -> { + X509Certificate[] peerCerts = certificates(chain); + Cipher cipher = Cipher.valueOf(cipherNo); + String auth = cipher == null ? "RSA" : cipher.getAu().toString(); + try { + if(server) { + manager.checkClientTrusted(peerCerts, auth); + } else { + manager.checkServerTrusted(peerCerts, auth); + } + return true; + } catch (Exception e) { + if (LOG.isLoggable(Level.FINE)) { + LOG.log(Level.FINE, "Certificate verification failed", e); + } + } + return false; + }); + } + } + private X509KeyManager chooseKeyManager(KeyManager[] tms) { if(tms == null) { return null; @@ -529,50 +548,41 @@ public void sessionRemoved(byte[] session) { serverSessionContext.remove(session); } - private ArrayList getHostnames(X509Certificate cert) { + private SNIMatcher getHostnamesSNIMatcher(final X509Certificate cert) { if (cert == null) { - Collections.emptyList(); + return SNIHostName.createSNIMatcher(""); } - final ArrayList hostnames = new ArrayList<>(); - final String certSubjectDN = cert.getSubjectX500Principal().getName(); + final StringBuilder builder = new StringBuilder(); // extract all the valid "hostnames" from the SANs try { final Collection> sansList = cert.getSubjectAlternativeNames(); - if (sansList != null && sansList.size() > 0) { - for (List san : sansList) { - switch ((Integer) san.get(0)) { - case 2: // DNS - case 7: // IP - Object sanData = san.get(1); - if (sanData instanceof String) { - hostnames.add((String) sanData); - } - break; - default: + + if (sansList != null && !sansList.isEmpty()) { + for (final List san : sansList) { + if ((Integer) san.get(0) == 2) { // DNS + final Object sanData = san.get(1); + + if (sanData instanceof String) { + builder.append(Pattern.quote((String) sanData)); + builder.append("|"); + } } } } - } catch (CertificateParsingException ex) { - final String msg = String.format("Unable to parse SANS of own certificate [%s].", certSubjectDN); + } catch (final CertificateParsingException ex) { + final String msg = String.format("Unable to parse SANS of own certificate [%s].", cert.getSubjectX500Principal().getName()); LOG.log(Level.WARNING, msg, ex); } - // extract the commonName from the subject - try { - final LdapName ldapName = new LdapName(certSubjectDN); - final Rdn commonNameRdn = ldapName.getRdns().stream() - .filter(item -> item.getType().equalsIgnoreCase("CN")) - .findFirst() - .orElse(null); - hostnames.add((String) commonNameRdn.getValue()); - } catch (InvalidNameException ex) { - final String msg = String.format("Unable to parse subject of own certificate [%s].", certSubjectDN); - LOG.log(Level.WARNING, msg, ex); + final int len = builder.length(); + + if (len > 0 && builder.charAt(len - 1) == '|') { + builder.deleteCharAt(len - 1); } - return hostnames; + return SNIHostName.createSNIMatcher(builder.toString()); } public static final class OpenSSLTLSContextSpi extends OpenSSLContextSPI { diff --git a/java/src/main/java/org/wildfly/openssl/OpenSSLEngine.java b/java/src/main/java/org/wildfly/openssl/OpenSSLEngine.java index 2c30bee5..377f766f 100644 --- a/java/src/main/java/org/wildfly/openssl/OpenSSLEngine.java +++ b/java/src/main/java/org/wildfly/openssl/OpenSSLEngine.java @@ -1077,44 +1077,43 @@ private void beginHandshakeImplicitly() throws SSLException { } } + protected String alpnCallback(final String[] data) { + String version = SSL.getInstance().getVersion(ssl); + if((protocolSelector == null && applicationProtocols == null) || version == null || ! (version.equals("TLSv1.2") || version.equals("TLSv1.3"))) { + //only offer ALPN on TLS 1.2+, try and force http/1.1 if it is offered, otherwise fail the connection + //it seems wrong to hard code protocols in the SSL impl, but openssl does not really allow alpn to be enabled + //on a per engine basis + for(String i : data) { + if(i.equals("http/1.1")) { + return i; + } + } + selectedApplicationProtocol = ""; + return null; + } + if (protocolSelector != null) { + selectedApplicationProtocol = protocolSelector.apply(OpenSSLEngine.this, Arrays.asList(data)); + return selectedApplicationProtocol; + } + + for (String proto : applicationProtocols) { + for (String clientProto : data) { + if (clientProto.equals(proto)) { + selectedApplicationProtocol = proto; + return proto; + } + } + } + selectedApplicationProtocol = ""; + return null; + } + private void handshake() throws SSLException { initSsl(); if (!alpnRegistered) { alpnRegistered = true; if (!isClientMode()) { - SSL.getInstance().setServerALPNCallback(ssl, new ServerALPNCallback() { - @Override - public String select(String[] data) { - String version = SSL.getInstance().getVersion(ssl); - if((protocolSelector == null && applicationProtocols == null) || version == null || ! (version.equals("TLSv1.2") || version.equals("TLSv1.3"))) { - //only offer ALPN on TLS 1.2+, try and force http/1.1 if it is offered, otherwise fail the connection - //it seems wrong to hard code protocols in the SSL impl, but openssl does not really allow alpn to be enabled - //on a per engine basis - for(String i : data) { - if(i.equals("http/1.1")) { - return i; - } - } - selectedApplicationProtocol = ""; - return null; - } - if (protocolSelector != null) { - selectedApplicationProtocol = protocolSelector.apply(OpenSSLEngine.this, Arrays.asList(data)); - return selectedApplicationProtocol; - } - - for (String proto : applicationProtocols) { - for (String clientProto : data) { - if (clientProto.equals(proto)) { - selectedApplicationProtocol = proto; - return proto; - } - } - } - selectedApplicationProtocol = ""; - return null; - } - }); + SSL.getInstance().setServerALPNCallback(ssl, new OpenSSLEngineServerALPNCallback(this)); } else if(applicationProtocols != null){ SSL.getInstance().setAlpnProtos(ssl, applicationProtocols); } diff --git a/java/src/main/java/org/wildfly/openssl/OpenSSLEngineServerALPNCallback.java b/java/src/main/java/org/wildfly/openssl/OpenSSLEngineServerALPNCallback.java new file mode 100644 index 00000000..10385eb4 --- /dev/null +++ b/java/src/main/java/org/wildfly/openssl/OpenSSLEngineServerALPNCallback.java @@ -0,0 +1,21 @@ +package org.wildfly.openssl; + +import java.lang.ref.WeakReference; + +public class OpenSSLEngineServerALPNCallback implements ServerALPNCallback { + private WeakReference sslEngineReference; + + public OpenSSLEngineServerALPNCallback(final OpenSSLEngine sslEngine) { + this.sslEngineReference = new WeakReference<>(sslEngine); + } + + @Override + public String select(String[] protocols) { + final OpenSSLEngine sslEngine = this.sslEngineReference.get(); + if (sslEngine == null) { + return null; + } + + return sslEngine.alpnCallback(protocols); + } +}