From f5448c5faa94371192861b77aa297b5f6b357cd5 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Mon, 25 May 2020 14:47:36 +0200 Subject: [PATCH] [core] Rework TLS logic This moves the logic in AbstractXMPPConnection.getSmackTlsContext() into the ConnectionConfiguration constructor. Also introduce SslContextFactory and use it in ConnectionConfiguration. --- .../smack/AbstractXMPPConnection.java | 175 +------------ .../smack/ConnectionConfiguration.java | 234 +++++++++++++----- .../ModularXmppClientToServerConnection.java | 10 +- ...rXmppClientToServerConnectionInternal.java | 13 +- .../smack/internal/SmackTlsContext.java | 32 +++ .../smack/util/SslContextFactory.java | 25 ++ .../org/jivesoftware/smack/util/TLSUtils.java | 33 +-- .../httpfileupload/HttpFileUploadManager.java | 6 - .../smack/inttest/AbstractSmackIntTest.java | 4 +- .../smack/inttest/Configuration.java | 12 +- .../HttpFileUploadIntegrationTest.java | 4 +- .../smack/smackrepl/TlsTest.java | 2 +- .../smack/tcp/XMPPTCPConnection.java | 1 + .../smack/tcp/XmppTcpTransportModule.java | 15 +- 14 files changed, 252 insertions(+), 314 deletions(-) create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/internal/SmackTlsContext.java create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/util/SslContextFactory.java diff --git a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java index eed428deb..136976a09 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java @@ -16,24 +16,9 @@ */ package org.jivesoftware.smack; -import java.io.ByteArrayInputStream; -import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStream; import java.io.Reader; import java.io.Writer; -import java.lang.reflect.Constructor; -import java.nio.charset.StandardCharsets; -import java.security.KeyManagementException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.Provider; -import java.security.SecureRandom; -import java.security.Security; -import java.security.UnrecoverableKeyException; -import java.security.cert.CertificateException; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; @@ -56,18 +41,9 @@ import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.Logger; -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; -import javax.security.auth.callback.Callback; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.callback.PasswordCallback; import javax.xml.namespace.QName; -import org.jivesoftware.smack.ConnectionConfiguration.DnssecMode; import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode; import org.jivesoftware.smack.SmackConfiguration.UnknownIqRequestReplyMode; import org.jivesoftware.smack.SmackException.AlreadyConnectedException; @@ -92,6 +68,7 @@ import org.jivesoftware.smack.debugger.SmackDebuggerFactory; import org.jivesoftware.smack.filter.IQReplyFilter; import org.jivesoftware.smack.filter.StanzaFilter; import org.jivesoftware.smack.filter.StanzaIdFilter; +import org.jivesoftware.smack.internal.SmackTlsContext; import org.jivesoftware.smack.iqrequest.IQRequestHandler; import org.jivesoftware.smack.packet.Bind; import org.jivesoftware.smack.packet.ErrorIQ; @@ -126,19 +103,14 @@ import org.jivesoftware.smack.sasl.SASLMechanism; import org.jivesoftware.smack.sasl.core.SASLAnonymous; import org.jivesoftware.smack.sasl.packet.SaslNonza; import org.jivesoftware.smack.util.Async; -import org.jivesoftware.smack.util.CloseableUtil; import org.jivesoftware.smack.util.CollectionUtil; import org.jivesoftware.smack.util.Consumer; -import org.jivesoftware.smack.util.DNSUtil; import org.jivesoftware.smack.util.MultiMap; import org.jivesoftware.smack.util.Objects; import org.jivesoftware.smack.util.PacketParserUtils; import org.jivesoftware.smack.util.ParserUtils; import org.jivesoftware.smack.util.Predicate; import org.jivesoftware.smack.util.StringUtils; -import org.jivesoftware.smack.util.TLSUtils; -import org.jivesoftware.smack.util.dns.SmackDaneProvider; -import org.jivesoftware.smack.util.dns.SmackDaneVerifier; import org.jivesoftware.smack.xml.XmlPullParser; import org.jivesoftware.smack.xml.XmlPullParserException; @@ -2203,148 +2175,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { outgoingStreamXmlEnvironment = xmlEnvironmentBuilder.build(); } - public static final class SmackTlsContext { - public final SSLContext sslContext; - public final SmackDaneVerifier daneVerifier; - - private SmackTlsContext(SSLContext sslContext, SmackDaneVerifier daneVerifier) { - assert sslContext != null; - this.sslContext = sslContext; - this.daneVerifier = daneVerifier; - } - } - - protected final SmackTlsContext getSmackTlsContext() throws KeyManagementException, NoSuchAlgorithmException, - CertificateException, IOException, UnrecoverableKeyException, KeyStoreException, NoSuchProviderException { - SmackDaneVerifier daneVerifier = null; - - if (config.getDnssecMode() == DnssecMode.needsDnssecAndDane) { - SmackDaneProvider daneProvider = DNSUtil.getDaneProvider(); - if (daneProvider == null) { - throw new UnsupportedOperationException("DANE enabled but no SmackDaneProvider configured"); - } - daneVerifier = daneProvider.newInstance(); - if (daneVerifier == null) { - throw new IllegalStateException("DANE requested but DANE provider did not return a DANE verifier"); - } - } - - SSLContext context = this.config.getCustomSSLContext(); - KeyStore ks = null; - PasswordCallback pcb = null; - - if (context == null) { - final String keyStoreType = config.getKeystoreType(); - final CallbackHandler callbackHandler = config.getCallbackHandler(); - final String keystorePath = config.getKeystorePath(); - if ("PKCS11".equals(keyStoreType)) { - try { - Constructor c = Class.forName("sun.security.pkcs11.SunPKCS11").getConstructor(InputStream.class); - String pkcs11Config = "name = SmartCard\nlibrary = " + config.getPKCS11Library(); - ByteArrayInputStream config = new ByteArrayInputStream(pkcs11Config.getBytes(StandardCharsets.UTF_8)); - Provider p = (Provider) c.newInstance(config); - Security.addProvider(p); - ks = KeyStore.getInstance("PKCS11", p); - pcb = new PasswordCallback("PKCS11 Password: ", false); - callbackHandler.handle(new Callback[] {pcb}); - ks.load(null, pcb.getPassword()); - } - catch (Exception e) { - LOGGER.log(Level.WARNING, "Exception", e); - ks = null; - } - } - else if ("Apple".equals(keyStoreType)) { - ks = KeyStore.getInstance("KeychainStore", "Apple"); - ks.load(null, null); - // pcb = new PasswordCallback("Apple Keychain",false); - // pcb.setPassword(null); - } - else if (keyStoreType != null) { - ks = KeyStore.getInstance(keyStoreType); - if (callbackHandler != null && StringUtils.isNotEmpty(keystorePath)) { - try { - pcb = new PasswordCallback("Keystore Password: ", false); - callbackHandler.handle(new Callback[] { pcb }); - ks.load(new FileInputStream(keystorePath), pcb.getPassword()); - } - catch (Exception e) { - LOGGER.log(Level.WARNING, "Exception", e); - ks = null; - } - } else { - InputStream stream = TLSUtils.getDefaultTruststoreStreamIfPossible(); - try { - // Note that PKCS12 keystores need a password one some Java platforms. Hence we try the famous - // 'changeit' here. See https://bugs.openjdk.java.net/browse/JDK-8194702 - char[] password = "changeit".toCharArray(); - try { - ks.load(stream, password); - } finally { - CloseableUtil.maybeClose(stream); - } - } catch (IOException e) { - LOGGER.log(Level.FINE, "KeyStore load() threw, attempting 'jks' fallback", e); - - ks = KeyStore.getInstance("jks"); - // Open the stream again, so that we read it from the beginning. - stream = TLSUtils.getDefaultTruststoreStreamIfPossible(); - try { - ks.load(stream, null); - } finally { - CloseableUtil.maybeClose(stream); - } - } - } - } - - KeyManager[] kms = null; - - if (ks != null) { - String keyManagerFactoryAlgorithm = KeyManagerFactory.getDefaultAlgorithm(); - KeyManagerFactory kmf = null; - try { - kmf = KeyManagerFactory.getInstance(keyManagerFactoryAlgorithm); - } - catch (NoSuchAlgorithmException e) { - LOGGER.log(Level.FINE, "Could get the default KeyManagerFactory for the '" - + keyManagerFactoryAlgorithm + "' algorithm", e); - } - if (kmf != null) { - try { - if (pcb == null) { - kmf.init(ks, null); - } - else { - kmf.init(ks, pcb.getPassword()); - pcb.clearPassword(); - } - kms = kmf.getKeyManagers(); - } - catch (NullPointerException npe) { - LOGGER.log(Level.WARNING, "NullPointerException", npe); - } - } - } - - // If the user didn't specify a SSLContext, use the default one - context = SSLContext.getInstance("TLS"); - - final SecureRandom secureRandom = new java.security.SecureRandom(); - X509TrustManager trustManager = config.getCustomX509TrustManager(); - if (trustManager == null) { - trustManager = TLSUtils.getDefaultX509TrustManager(ks); - } - - if (daneVerifier != null) { - // User requested DANE verification. - daneVerifier.init(context, kms, trustManager, secureRandom); - } else { - TrustManager[] customTrustManagers = new TrustManager[] { trustManager }; - context.init(kms, customTrustManagers, secureRandom); - } - } - - return new SmackTlsContext(context, daneVerifier); + protected final SmackTlsContext getSmackTlsContext() { + return config.smackTlsContext; } } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/ConnectionConfiguration.java b/smack-core/src/main/java/org/jivesoftware/smack/ConnectionConfiguration.java index 89b6ec278..e6009754b 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/ConnectionConfiguration.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/ConnectionConfiguration.java @@ -17,9 +17,24 @@ package org.jivesoftware.smack; +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.net.InetAddress; import java.net.UnknownHostException; +import java.nio.charset.StandardCharsets; +import java.security.KeyManagementException; import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Provider; +import java.security.Security; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -31,21 +46,34 @@ import java.util.logging.Logger; import javax.net.SocketFactory; import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; +import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; import org.jivesoftware.smack.datatypes.UInt16; import org.jivesoftware.smack.debugger.SmackDebuggerFactory; +import org.jivesoftware.smack.internal.SmackTlsContext; import org.jivesoftware.smack.packet.id.StandardStanzaIdSource; import org.jivesoftware.smack.packet.id.StanzaIdSource; import org.jivesoftware.smack.packet.id.StanzaIdSourceFactory; import org.jivesoftware.smack.proxy.ProxyInfo; import org.jivesoftware.smack.sasl.SASLMechanism; import org.jivesoftware.smack.sasl.core.SASLAnonymous; +import org.jivesoftware.smack.util.CloseableUtil; import org.jivesoftware.smack.util.CollectionUtil; +import org.jivesoftware.smack.util.DNSUtil; import org.jivesoftware.smack.util.Objects; +import org.jivesoftware.smack.util.SslContextFactory; import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smack.util.TLSUtils; +import org.jivesoftware.smack.util.dns.SmackDaneProvider; +import org.jivesoftware.smack.util.dns.SmackDaneVerifier; import org.jxmpp.jid.DomainBareJid; import org.jxmpp.jid.EntityBareJid; @@ -104,11 +132,6 @@ public abstract class ConnectionConfiguration { protected final DnsName host; protected final UInt16 port; - private final String keystorePath; - private final String keystoreType; - private final String pkcs11Library; - private final SSLContext customSSLContext; - /** * Used to get information from the user */ @@ -138,9 +161,9 @@ public abstract class ConnectionConfiguration { private final SecurityMode securityMode; - private final DnssecMode dnssecMode; + final SmackTlsContext smackTlsContext; - private final X509TrustManager customX509TrustManager; + private final DnssecMode dnssecMode; /** * @@ -166,6 +189,17 @@ public abstract class ConnectionConfiguration { private final StanzaIdSourceFactory stanzaIdSourceFactory; protected ConnectionConfiguration(Builder builder) { + try { + smackTlsContext = getSmackTlsContext(builder.dnssecMode, builder.sslContextFactory, + builder.customX509TrustManager, builder.keystoreType, builder.keystorePath, + builder.callbackHandler, builder.pkcs11Library); + } catch (UnrecoverableKeyException | KeyManagementException | NoSuchAlgorithmException | CertificateException + | KeyStoreException | NoSuchProviderException | IOException | NoSuchMethodException + | SecurityException | ClassNotFoundException | InstantiationException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException | UnsupportedCallbackException e) { + throw new IllegalArgumentException(e); + } + authzid = builder.authzid; username = builder.username; password = builder.password; @@ -202,13 +236,7 @@ public abstract class ConnectionConfiguration { dnssecMode = builder.dnssecMode; - customX509TrustManager = builder.customX509TrustManager; - securityMode = builder.securityMode; - keystoreType = builder.keystoreType; - keystorePath = builder.keystorePath; - pkcs11Library = builder.pkcs11Library; - customSSLContext = builder.customSSLContext; enabledSSLProtocols = builder.enabledSSLProtocols; enabledSSLCiphers = builder.enabledSSLCiphers; hostnameVerifier = builder.hostnameVerifier; @@ -223,11 +251,115 @@ public abstract class ConnectionConfiguration { // If the enabledSaslmechanisms are set, then they must not be empty assert enabledSaslMechanisms == null || !enabledSaslMechanisms.isEmpty(); + } - if (dnssecMode != DnssecMode.disabled && customSSLContext != null) { - throw new IllegalStateException("You can not use a custom SSL context with DNSSEC enabled"); + private static SmackTlsContext getSmackTlsContext(DnssecMode dnssecMode, SslContextFactory sslContextFactory, + X509TrustManager trustManager, String keystoreType, String keystorePath, + CallbackHandler callbackHandler, String pkcs11Library) throws NoSuchAlgorithmException, + CertificateException, IOException, KeyStoreException, NoSuchProviderException, + UnrecoverableKeyException, KeyManagementException, UnsupportedCallbackException, + NoSuchMethodException, SecurityException, ClassNotFoundException, InstantiationException, + IllegalAccessException, IllegalArgumentException, InvocationTargetException { + final SSLContext context; + if (sslContextFactory != null) { + context = sslContextFactory.createSslContext(); + } else { + // If the user didn't specify a SslContextFactory, use the default one + context = SSLContext.getInstance("TLS"); } + KeyStore ks = null; + PasswordCallback pcb = null; + KeyManager[] kms = null; + + if ("PKCS11".equals(keystoreType)) { + Constructor c = Class.forName("sun.security.pkcs11.SunPKCS11").getConstructor(InputStream.class); + String pkcs11Config = "name = SmartCard\nlibrary = " + pkcs11Library; + ByteArrayInputStream config = new ByteArrayInputStream(pkcs11Config.getBytes(StandardCharsets.UTF_8)); + Provider p = (Provider) c.newInstance(config); + Security.addProvider(p); + ks = KeyStore.getInstance("PKCS11", p); + pcb = new PasswordCallback("PKCS11 Password: ", false); + callbackHandler.handle(new Callback[] { pcb }); + ks.load(null, pcb.getPassword()); + } else if ("Apple".equals(keystoreType)) { + ks = KeyStore.getInstance("KeychainStore", "Apple"); + ks.load(null, null); + // pcb = new PasswordCallback("Apple Keychain",false); + // pcb.setPassword(null); + } else if (keystoreType != null) { + ks = KeyStore.getInstance(keystoreType); + if (callbackHandler != null && StringUtils.isNotEmpty(keystorePath)) { + pcb = new PasswordCallback("Keystore Password: ", false); + callbackHandler.handle(new Callback[] { pcb }); + ks.load(new FileInputStream(keystorePath), pcb.getPassword()); + } else { + InputStream stream = TLSUtils.getDefaultTruststoreStreamIfPossible(); + try { + // Note that PKCS12 keystores need a password one some Java platforms. Hence we try the famous + // 'changeit' here. See https://bugs.openjdk.java.net/browse/JDK-8194702 + char[] password = "changeit".toCharArray(); + try { + ks.load(stream, password); + } finally { + CloseableUtil.maybeClose(stream); + } + } catch (IOException e) { + LOGGER.log(Level.FINE, "KeyStore load() threw, attempting 'jks' fallback", e); + + ks = KeyStore.getInstance("jks"); + // Open the stream again, so that we read it from the beginning. + stream = TLSUtils.getDefaultTruststoreStreamIfPossible(); + try { + ks.load(stream, null); + } finally { + CloseableUtil.maybeClose(stream); + } + } + } + } + + if (ks != null) { + String keyManagerFactoryAlgorithm = KeyManagerFactory.getDefaultAlgorithm(); + KeyManagerFactory kmf = KeyManagerFactory.getInstance(keyManagerFactoryAlgorithm); + if (kmf != null) { + if (pcb == null) { + kmf.init(ks, null); + } else { + kmf.init(ks, pcb.getPassword()); + pcb.clearPassword(); + } + kms = kmf.getKeyManagers(); + } + } + + SmackDaneVerifier daneVerifier = null; + if (dnssecMode == DnssecMode.needsDnssecAndDane) { + SmackDaneProvider daneProvider = DNSUtil.getDaneProvider(); + if (daneProvider == null) { + throw new UnsupportedOperationException("DANE enabled but no SmackDaneProvider configured"); + } + daneVerifier = daneProvider.newInstance(); + if (daneVerifier == null) { + throw new IllegalStateException("DANE requested but DANE provider did not return a DANE verifier"); + } + + // User requested DANE verification. + daneVerifier.init(context, kms, trustManager, null); + } else { + final TrustManager[] trustManagers; + if (trustManager != null) { + trustManagers = new TrustManager[] { trustManager }; + } else { + // Ensure trustManagers is null in case there was no explicit trust manager provided, so that the + // default one is used. + trustManagers = null; + } + + context.init(kms, trustManagers, null); + } + + return new SmackTlsContext(context, daneVerifier); } public DnsName getHost() { @@ -287,50 +419,6 @@ public abstract class ConnectionConfiguration { return dnssecMode; } - public X509TrustManager getCustomX509TrustManager() { - return customX509TrustManager; - } - - /** - * Retuns the path to the keystore file. The key store file contains the - * certificates that may be used to authenticate the client to the server, - * in the event the server requests or requires it. - * - * @return the path to the keystore file. - */ - public String getKeystorePath() { - return keystorePath; - } - - /** - * Returns the keystore type, or null if it's not set. - * - * @return the keystore type. - */ - public String getKeystoreType() { - return keystoreType; - } - - /** - * Returns the PKCS11 library file location, needed when the - * Keystore type is PKCS11. - * - * @return the path to the PKCS11 library file - */ - public String getPKCS11Library() { - return pkcs11Library; - } - - /** - * Gets the custom SSLContext previously set with {@link ConnectionConfiguration.Builder#setCustomSSLContext(SSLContext)} for - * SSL sockets. This is null by default. - * - * @return the custom SSLContext or null. - */ - public SSLContext getCustomSSLContext() { - return this.customSSLContext; - } - /** * Return the enabled SSL/TLS protocols. * @@ -602,10 +690,10 @@ public abstract class ConnectionConfiguration { public abstract static class Builder, C extends ConnectionConfiguration> { private SecurityMode securityMode = SecurityMode.required; private DnssecMode dnssecMode = DnssecMode.disabled; - private String keystorePath = System.getProperty("javax.net.ssl.keyStore"); - private String keystoreType = KeyStore.getDefaultType(); + private String keystorePath; + private String keystoreType; private String pkcs11Library = "pkcs11.config"; - private SSLContext customSSLContext; + private SslContextFactory sslContextFactory; private String[] enabledSSLProtocols; private String[] enabledSSLCiphers; private HostnameVerifier hostnameVerifier; @@ -929,9 +1017,28 @@ public abstract class ConnectionConfiguration { * * @param context the custom SSLContext for new sockets. * @return a reference to this builder. + * @deprecated use {@link #setSslContextFactory(SslContextFactory)} instead}. */ + // TODO: Remove in Smack 4.5. + @Deprecated public B setCustomSSLContext(SSLContext context) { - this.customSSLContext = Objects.requireNonNull(context, "The SSLContext must not be null"); + return setSslContextFactory(() -> { + return context; + }); + } + + /** + * Sets a custom SSLContext for creating SSL sockets. + *

+ * For more information on how to create a SSLContext see Java Secure Socket Extension (JSEE) Reference Guide: Creating Your Own X509TrustManager + * + * @param sslContextFactory the custom SSLContext for new sockets. + * @return a reference to this builder. + */ + public B setSslContextFactory(SslContextFactory sslContextFactory) { + this.sslContextFactory = Objects.requireNonNull(sslContextFactory, "The provided SslContextFactory must not be null"); return getThis(); } @@ -1172,5 +1279,4 @@ public abstract class ConnectionConfiguration { protected abstract B getThis(); } - } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java b/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java index eff02dedb..061d25e7a 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java @@ -17,11 +17,6 @@ package org.jivesoftware.smack.c2s; import java.io.IOException; -import java.security.KeyManagementException; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.Collection; @@ -66,6 +61,7 @@ import org.jivesoftware.smack.fsm.StateMachineException; import org.jivesoftware.smack.fsm.StateTransitionResult; import org.jivesoftware.smack.fsm.StateTransitionResult.AttemptResult; import org.jivesoftware.smack.internal.AbstractStats; +import org.jivesoftware.smack.internal.SmackTlsContext; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Nonza; @@ -186,9 +182,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne } @Override - public SmackTlsContext getSmackTlsContext() - throws KeyManagementException, NoSuchAlgorithmException, CertificateException, IOException, - UnrecoverableKeyException, KeyStoreException, NoSuchProviderException { + public SmackTlsContext getSmackTlsContext() { return ModularXmppClientToServerConnection.this.getSmackTlsContext(); } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/c2s/internal/ModularXmppClientToServerConnectionInternal.java b/smack-core/src/main/java/org/jivesoftware/smack/c2s/internal/ModularXmppClientToServerConnectionInternal.java index 57452f270..b08676914 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/c2s/internal/ModularXmppClientToServerConnectionInternal.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/c2s/internal/ModularXmppClientToServerConnectionInternal.java @@ -16,20 +16,12 @@ */ package org.jivesoftware.smack.c2s.internal; -import java.io.IOException; import java.nio.channels.ClosedChannelException; import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; -import java.security.KeyManagementException; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.UnrecoverableKeyException; -import java.security.cert.CertificateException; import java.util.ListIterator; import java.util.Queue; -import org.jivesoftware.smack.AbstractXMPPConnection.SmackTlsContext; import org.jivesoftware.smack.SmackException.ConnectionUnexpectedTerminatedException; import org.jivesoftware.smack.SmackException.NoResponseException; import org.jivesoftware.smack.SmackException.NotConnectedException; @@ -41,6 +33,7 @@ import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection; import org.jivesoftware.smack.c2s.XmppClientToServerTransport; import org.jivesoftware.smack.debugger.SmackDebugger; import org.jivesoftware.smack.fsm.ConnectionStateEvent; +import org.jivesoftware.smack.internal.SmackTlsContext; import org.jivesoftware.smack.packet.Nonza; import org.jivesoftware.smack.packet.TopLevelStreamElement; import org.jivesoftware.smack.packet.XmlEnvironment; @@ -107,9 +100,7 @@ public abstract class ModularXmppClientToServerConnectionInternal { public abstract void newStreamOpenWaitForFeaturesSequence(String waitFor) throws InterruptedException, ConnectionUnexpectedTerminatedException, NoResponseException, NotConnectedException; - public abstract SmackTlsContext getSmackTlsContext() - throws KeyManagementException, NoSuchAlgorithmException, CertificateException, IOException, - UnrecoverableKeyException, KeyStoreException, NoSuchProviderException; + public abstract SmackTlsContext getSmackTlsContext(); public abstract SN sendAndWaitForResponse(Nonza nonza, Class successNonzaClass, Class failedNonzaClass) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/internal/SmackTlsContext.java b/smack-core/src/main/java/org/jivesoftware/smack/internal/SmackTlsContext.java new file mode 100644 index 000000000..b0425b162 --- /dev/null +++ b/smack-core/src/main/java/org/jivesoftware/smack/internal/SmackTlsContext.java @@ -0,0 +1,32 @@ +/** + * + * Copyright 2020 Florian Schmaus + * + * 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.jivesoftware.smack.internal; + +import javax.net.ssl.SSLContext; + +import org.jivesoftware.smack.util.dns.SmackDaneVerifier; + +public final class SmackTlsContext { + public final SSLContext sslContext; + public final SmackDaneVerifier daneVerifier; + + public SmackTlsContext(SSLContext sslContext, SmackDaneVerifier daneVerifier) { + assert sslContext != null; + this.sslContext = sslContext; + this.daneVerifier = daneVerifier; + } +} diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/SslContextFactory.java b/smack-core/src/main/java/org/jivesoftware/smack/util/SslContextFactory.java new file mode 100644 index 000000000..a3c308b9f --- /dev/null +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/SslContextFactory.java @@ -0,0 +1,25 @@ +/** + * + * Copyright 2020 Florian Schmaus + * + * 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.jivesoftware.smack.util; + +import javax.net.ssl.SSLContext; + +public interface SslContextFactory { + + SSLContext createSslContext(); + +} diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/TLSUtils.java b/smack-core/src/main/java/org/jivesoftware/smack/util/TLSUtils.java index 69b817566..c1af8ba61 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/TLSUtils.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/TLSUtils.java @@ -22,12 +22,8 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import java.security.KeyManagementException; -import java.security.KeyStore; -import java.security.KeyStoreException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; @@ -38,12 +34,9 @@ import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; -import javax.net.ssl.SSLContext; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; import org.jivesoftware.smack.ConnectionConfiguration; @@ -131,14 +124,10 @@ public class TLSUtils { * * @param builder a connection configuration builder. * @param Type of the ConnectionConfiguration builder. - * @throws NoSuchAlgorithmException if no such algorithm is available. - * @throws KeyManagementException if there was a key mangement error. * @return the given builder. */ - public static > B acceptAllCertificates(B builder) throws NoSuchAlgorithmException, KeyManagementException { - SSLContext context = SSLContext.getInstance(TLS); - context.init(null, new TrustManager[] { new AcceptAllTrustManager() }, new SecureRandom()); - builder.setCustomSSLContext(context); + public static > B acceptAllCertificates(B builder) { + builder.setCustomX509TrustManager(new AcceptAllTrustManager()); return builder; } @@ -267,24 +256,6 @@ public class TLSUtils { } } - public static X509TrustManager getDefaultX509TrustManager(KeyStore keyStore) { - String defaultAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); - TrustManagerFactory trustManagerFactory; - try { - trustManagerFactory = TrustManagerFactory.getInstance(defaultAlgorithm); - trustManagerFactory.init(keyStore); - } catch (NoSuchAlgorithmException | KeyStoreException e) { - throw new AssertionError(e); - } - - for (TrustManager trustManager : trustManagerFactory.getTrustManagers()) { - if (trustManager instanceof X509TrustManager) { - return (X509TrustManager) trustManager; - } - } - throw new AssertionError("No trust manager for the default algorithm " + defaultAlgorithm + " found"); - } - private static final File DEFAULT_TRUSTSTORE_PATH; static { diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/HttpFileUploadManager.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/HttpFileUploadManager.java index 8a12f164c..23379a4cc 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/HttpFileUploadManager.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/HttpFileUploadManager.java @@ -37,7 +37,6 @@ import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; -import org.jivesoftware.smack.ConnectionConfiguration; import org.jivesoftware.smack.ConnectionCreationListener; import org.jivesoftware.smack.ConnectionListener; import org.jivesoftware.smack.Manager; @@ -428,11 +427,6 @@ public final class HttpFileUploadManager extends Manager { this.tlsSocketFactory = tlsContext.getSocketFactory(); } - public void useTlsSettingsFrom(ConnectionConfiguration connectionConfiguration) { - SSLContext sslContext = connectionConfiguration.getCustomSSLContext(); - setTlsContext(sslContext); - } - private void upload(InputStream iStream, long fileSize, Slot slot, UploadProgressListener listener) throws IOException { final URL putUrl = slot.getPutUrl(); diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackIntTest.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackIntTest.java index 2bdb340bd..2b94b2190 100644 --- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackIntTest.java +++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackIntTest.java @@ -84,9 +84,9 @@ public abstract class AbstractSmackIntTest { protected HttpURLConnection getHttpUrlConnectionFor(URL url) throws IOException { HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); - if (sinttestConfiguration.tlsContext != null && urlConnection instanceof HttpsURLConnection) { + if (sinttestConfiguration.sslContextFactory != null && urlConnection instanceof HttpsURLConnection) { HttpsURLConnection httpsUrlConnection = (HttpsURLConnection) urlConnection; - httpsUrlConnection.setSSLSocketFactory(sinttestConfiguration.tlsContext.getSocketFactory()); + httpsUrlConnection.setSSLSocketFactory(sinttestConfiguration.sslContextFactory.createSslContext().getSocketFactory()); } return urlConnection; } diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/Configuration.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/Configuration.java index 258fb3c1f..9f5c32633 100644 --- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/Configuration.java +++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/Configuration.java @@ -36,6 +36,7 @@ import org.jivesoftware.smack.debugger.ConsoleDebugger; import org.jivesoftware.smack.util.Function; import org.jivesoftware.smack.util.Objects; import org.jivesoftware.smack.util.ParserUtils; +import org.jivesoftware.smack.util.SslContextFactory; import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smackx.debugger.EnhancedDebugger; @@ -72,7 +73,7 @@ public final class Configuration { public final String serviceTlsPin; - public final SSLContext tlsContext; + public final SslContextFactory sslContextFactory; public final SecurityMode securityMode; @@ -121,9 +122,10 @@ public final class Configuration { "'service' must be set. Either via 'properties' files or via system property 'sinttest.service'."); serviceTlsPin = builder.serviceTlsPin; if (serviceTlsPin != null) { - tlsContext = Java7Pinning.forPin(serviceTlsPin); + SSLContext sslContext = Java7Pinning.forPin(serviceTlsPin); + sslContextFactory = () -> sslContext; } else { - tlsContext = null; + sslContextFactory = null; } securityMode = builder.securityMode; if (builder.replyTimeout > 0) { @@ -168,8 +170,8 @@ public final class Configuration { this.testPackages = builder.testPackages; this.configurationApplier = b -> { - if (tlsContext != null) { - b.setCustomSSLContext(tlsContext); + if (sslContextFactory != null) { + b.setSslContextFactory(sslContextFactory); } b.setSecurityMode(securityMode); b.setXmppDomain(service); diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/httpfileupload/HttpFileUploadIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/httpfileupload/HttpFileUploadIntegrationTest.java index 7736c0657..243bfec36 100644 --- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/httpfileupload/HttpFileUploadIntegrationTest.java +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/httpfileupload/HttpFileUploadIntegrationTest.java @@ -57,7 +57,9 @@ public class HttpFileUploadIntegrationTest extends AbstractSmackIntegrationTest + " does not accept files of size " + FILE_SIZE + ". It only accepts files with a maximum size of " + uploadService.getMaxFileSize()); } - hfumOne.setTlsContext(environment.configuration.tlsContext); + if (environment.configuration.sslContextFactory != null) { + hfumOne.setTlsContext(environment.configuration.sslContextFactory.createSslContext()); + } } @SmackIntegrationTest diff --git a/smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/TlsTest.java b/smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/TlsTest.java index 557f1812d..08bd25b6d 100644 --- a/smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/TlsTest.java +++ b/smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/TlsTest.java @@ -89,7 +89,7 @@ public class TlsTest { if (StringUtils.isNotEmpty(tlsPin)) { SSLContext sslContext = Java7Pinning.forPin(tlsPin); - builder.setCustomSSLContext(sslContext); + builder.setSslContextFactory(() -> sslContext); } diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java index f71e3f196..ea34b9518 100644 --- a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java +++ b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java @@ -81,6 +81,7 @@ import org.jivesoftware.smack.compress.packet.Compressed; import org.jivesoftware.smack.compression.XMPPInputOutputStream; import org.jivesoftware.smack.datatypes.UInt16; import org.jivesoftware.smack.filter.StanzaFilter; +import org.jivesoftware.smack.internal.SmackTlsContext; import org.jivesoftware.smack.packet.Element; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.Message; diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XmppTcpTransportModule.java b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XmppTcpTransportModule.java index 3df54dc1a..74286fdd9 100644 --- a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XmppTcpTransportModule.java +++ b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XmppTcpTransportModule.java @@ -24,11 +24,6 @@ import java.nio.channels.ClosedChannelException; import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; -import java.security.KeyManagementException; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.Collection; @@ -50,7 +45,6 @@ import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLException; import javax.net.ssl.SSLSession; -import org.jivesoftware.smack.AbstractXMPPConnection.SmackTlsContext; import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.SmackException.ConnectionException; @@ -75,6 +69,7 @@ import org.jivesoftware.smack.debugger.SmackDebugger; import org.jivesoftware.smack.fsm.State; import org.jivesoftware.smack.fsm.StateDescriptor; import org.jivesoftware.smack.fsm.StateTransitionResult; +import org.jivesoftware.smack.internal.SmackTlsContext; import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.packet.StartTls; import org.jivesoftware.smack.packet.StreamOpen; @@ -867,13 +862,7 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM ConnectionUnexpectedTerminatedException, NoResponseException, NotConnectedException { connectionInternal.sendAndWaitForResponse(StartTls.INSTANCE, TlsProceed.class, TlsFailure.class); - SmackTlsContext smackTlsContext; - try { - smackTlsContext = connectionInternal.getSmackTlsContext(); - } catch (KeyManagementException | UnrecoverableKeyException | NoSuchAlgorithmException - | CertificateException | KeyStoreException | NoSuchProviderException e) { - throw new SmackWrappedException(e); - } + SmackTlsContext smackTlsContext = connectionInternal.getSmackTlsContext(); tlsState = new TlsState(smackTlsContext); connectionInternal.addXmppInputOutputFilter(tlsState);