From d50a291366f1b5a16011266dbf770c020d241eee Mon Sep 17 00:00:00 2001 From: Maik Scheibler Date: Thu, 21 Jun 2018 18:08:18 +0200 Subject: [PATCH] enable the usage of Netty OpenSSL provider implementation Signed-off-by: Maik Scheibler --- .gitignore | 1 + broker/build.gradle | 1 + .../java/io/moquette/BrokerConstants.java | 9 + .../DefaultMoquetteSslContextCreator.java | 189 ++++++++++++------ .../moquette/server/netty/NettyAcceptor.java | 84 ++++---- .../spi/security/ISslContextCreator.java | 4 +- .../server/ServerIntegrationOpenSSLTest.java | 60 ++++++ ...erverLowlevelMessagesIntegrationTests.java | 3 +- 8 files changed, 248 insertions(+), 103 deletions(-) create mode 100644 broker/src/test/java/io/moquette/server/ServerIntegrationOpenSSLTest.java diff --git a/.gitignore b/.gitignore index 6b04aa3e..d1d98431 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ broker/runner .settings/ .project .classpath +/*/bin/ *.swp *.class diff --git a/broker/build.gradle b/broker/build.gradle index deb82272..47e4fe21 100644 --- a/broker/build.gradle +++ b/broker/build.gradle @@ -34,4 +34,5 @@ dependencies { testCompile group: 'org.eclipse.jetty.websocket', name: 'websocket-client', version:'9.2.0.M1' testCompile group: 'com.h2database', name: 'h2', version:'1.4.191' + testRuntime group: 'io.netty', name: 'netty-tcnative', version: '2.0.10.Final', classifier: 'linux-x86_64' } diff --git a/broker/src/main/java/io/moquette/BrokerConstants.java b/broker/src/main/java/io/moquette/BrokerConstants.java index aba8e03c..a2e58db4 100644 --- a/broker/src/main/java/io/moquette/BrokerConstants.java +++ b/broker/src/main/java/io/moquette/BrokerConstants.java @@ -32,8 +32,17 @@ public final class BrokerConstants { + DEFAULT_MOQUETTE_STORE_MAP_DB_FILENAME; public static final String WEB_SOCKET_PORT_PROPERTY_NAME = "websocket_port"; public static final String WSS_PORT_PROPERTY_NAME = "secure_websocket_port"; + + /** + * Defines the SSL implementation to use, default to "JDK". + * @see io.netty.handler.ssl.SslProvider#name() + */ + public static final String SSL_PROVIDER = "ssl_provider"; public static final String SSL_PORT_PROPERTY_NAME = "ssl_port"; public static final String JKS_PATH_PROPERTY_NAME = "jks_path"; + + /** @see java.security.KeyStore#getInstance(String) for allowed types, default to "jks" */ + public static final String KEY_STORE_TYPE = "key_store_type"; public static final String KEY_STORE_PASSWORD_PROPERTY_NAME = "key_store_password"; public static final String KEY_MANAGER_PASSWORD_PROPERTY_NAME = "key_manager_password"; public static final String ALLOW_ANONYMOUS_PROPERTY_NAME = "allow_anonymous"; diff --git a/broker/src/main/java/io/moquette/server/DefaultMoquetteSslContextCreator.java b/broker/src/main/java/io/moquette/server/DefaultMoquetteSslContextCreator.java index 86ef3ca9..df981f47 100644 --- a/broker/src/main/java/io/moquette/server/DefaultMoquetteSslContextCreator.java +++ b/broker/src/main/java/io/moquette/server/DefaultMoquetteSslContextCreator.java @@ -16,19 +16,34 @@ package io.moquette.server; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.security.GeneralSecurityException; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.Collections; +import java.util.Objects; + import io.moquette.BrokerConstants; import io.moquette.server.config.IConfig; import io.moquette.spi.security.ISslContextCreator; +import io.netty.handler.ssl.ClientAuth; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.handler.ssl.SslProvider; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.TrustManagerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import java.io.*; -import java.net.URL; -import java.security.*; -import java.security.cert.CertificateException; /** * Moquette server implementation to load SSL certificate from local filesystem path configured in @@ -38,80 +53,123 @@ class DefaultMoquetteSslContextCreator implements ISslContextCreator { private static final Logger LOG = LoggerFactory.getLogger(DefaultMoquetteSslContextCreator.class); - private IConfig props; + private final IConfig props; DefaultMoquetteSslContextCreator(IConfig props) { - this.props = props; + this.props = Objects.requireNonNull(props); } @Override - public SSLContext initSSLContext() { + public SslContext initSSLContext() { LOG.info("Checking SSL configuration properties..."); - final String jksPath = props.getProperty(BrokerConstants.JKS_PATH_PROPERTY_NAME); - LOG.info("Initializing SSL context. KeystorePath = {}.", jksPath); - if (jksPath == null || jksPath.isEmpty()) { - // key_store_password or key_manager_password are empty - LOG.warn("The keystore path is null or empty. The SSL context won't be initialized."); - return null; - } - // if we have the port also the jks then keyStorePassword and keyManagerPassword - // has to be defined - final String keyStorePassword = props.getProperty(BrokerConstants.KEY_STORE_PASSWORD_PROPERTY_NAME); - final String keyManagerPassword = props.getProperty(BrokerConstants.KEY_MANAGER_PASSWORD_PROPERTY_NAME); - if (keyStorePassword == null || keyStorePassword.isEmpty()) { - // key_store_password or key_manager_password are empty - LOG.warn("The keystore password is null or empty. The SSL context won't be initialized."); - return null; - } - if (keyManagerPassword == null || keyManagerPassword.isEmpty()) { - // key_manager_password or key_manager_password are empty + final String keyPassword = props.getProperty(BrokerConstants.KEY_MANAGER_PASSWORD_PROPERTY_NAME); + if (keyPassword == null || keyPassword.isEmpty()) { LOG.warn("The key manager password is null or empty. The SSL context won't be initialized."); return null; } - // if client authentification is enabled a trustmanager needs to be - // added to the ServerContext - String sNeedsClientAuth = props.getProperty(BrokerConstants.NEED_CLIENT_AUTH, "false"); - boolean needsClientAuth = Boolean.valueOf(sNeedsClientAuth); - try { - LOG.info("Loading keystore. KeystorePath = {}.", jksPath); - InputStream jksInputStream = jksDatastore(jksPath); - SSLContext serverContext = SSLContext.getInstance("TLS"); - final KeyStore ks = KeyStore.getInstance("JKS"); - ks.load(jksInputStream, keyStorePassword.toCharArray()); - LOG.info("Initializing key manager..."); - final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - kmf.init(ks, keyManagerPassword.toCharArray()); - TrustManager[] trustManagers = null; - if (needsClientAuth) { - LOG.warn( - "Client authentication is enabled. " - + "The keystore will be used as a truststore. KeystorePath = {}.", - jksPath); - // use keystore as truststore, as server needs to trust certificates signed by the - // server certificates - TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - tmf.init(ks); - trustManagers = tmf.getTrustManagers(); + SslProvider sslProvider = getSSLProvider(); + KeyStore ks = loadKeyStore(); + SslContextBuilder contextBuilder; + switch (sslProvider) { + case JDK: + contextBuilder = builderWithJdkProvider(ks, keyPassword); + break; + case OPENSSL: + case OPENSSL_REFCNT: + contextBuilder = builderWithOpenSSLProvider(ks, keyPassword); + break; + default: + LOG.error("unsupported SSL provider {}", sslProvider); + return null; } - // init sslContext - LOG.info("Initializing SSL context..."); - serverContext.init(kmf.getKeyManagers(), trustManagers, null); + // if client authentification is enabled a trustmanager needs to be added to the ServerContext + String sNeedsClientAuth = props.getProperty(BrokerConstants.NEED_CLIENT_AUTH, "false"); + if (Boolean.valueOf(sNeedsClientAuth)) { + addClientAuthentication(ks, contextBuilder); + } + contextBuilder.sslProvider(sslProvider); + SslContext sslContext = contextBuilder.build(); LOG.info("The SSL context has been initialized successfully."); - - return serverContext; - } catch (NoSuchAlgorithmException | UnrecoverableKeyException | CertificateException | KeyStoreException - | KeyManagementException | IOException ex) { - LOG.error( - "Unable to initialize SSL context. Cause = {}, errorMessage = {}.", - ex.getCause(), - ex.getMessage()); + return sslContext; + } catch (GeneralSecurityException | IOException ex) { + LOG.error("Unable to initialize SSL context.", ex); return null; } } + private KeyStore loadKeyStore() throws IOException, GeneralSecurityException { + final String jksPath = props.getProperty(BrokerConstants.JKS_PATH_PROPERTY_NAME); + LOG.info("Initializing SSL context. KeystorePath = {}.", jksPath); + if (jksPath == null || jksPath.isEmpty()) { + LOG.warn("The keystore path is null or empty. The SSL context won't be initialized."); + return null; + } + final String keyStorePassword = props.getProperty(BrokerConstants.KEY_STORE_PASSWORD_PROPERTY_NAME); + if (keyStorePassword == null || keyStorePassword.isEmpty()) { + LOG.warn("The keystore password is null or empty. The SSL context won't be initialized."); + return null; + } + String ksType = props.getProperty(BrokerConstants.KEY_STORE_TYPE, "jks"); + final KeyStore keyStore = KeyStore.getInstance(ksType); + LOG.info("Loading keystore. KeystorePath = {}.", jksPath); + try (InputStream jksInputStream = jksDatastore(jksPath)) { + keyStore.load(jksInputStream, keyStorePassword.toCharArray()); + } + return keyStore; + } + + private static SslContextBuilder builderWithJdkProvider(KeyStore ks, String keyPassword) + throws GeneralSecurityException { + LOG.info("Initializing key manager..."); + final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(ks, keyPassword.toCharArray()); + LOG.info("Initializing SSL context..."); + return SslContextBuilder.forServer(kmf); + } + + /** + * The OpenSSL provider does not support the {@link KeyManagerFactory}, so we have to lookup the server + * certificate and key in order to provide it to OpenSSL. + *

+ * TODO: SNI is currently not supported, we use only the first found private key. + */ + private static SslContextBuilder builderWithOpenSSLProvider(KeyStore ks, String keyPassword) + throws GeneralSecurityException { + for (String alias : Collections.list(ks.aliases())) { + if (ks.entryInstanceOf(alias, KeyStore.PrivateKeyEntry.class)) { + PrivateKey key = (PrivateKey) ks.getKey(alias, keyPassword.toCharArray()); + Certificate[] chain = ks.getCertificateChain(alias); + X509Certificate[] certChain = new X509Certificate[chain.length]; + System.arraycopy(chain, 0, certChain, 0, chain.length); + return SslContextBuilder.forServer(key, certChain); + } + } + throw new KeyManagementException("the SSL key-store does not contain a private key"); + } + + private static void addClientAuthentication(KeyStore ks, SslContextBuilder contextBuilder) + throws NoSuchAlgorithmException, KeyStoreException { + LOG.warn("Client authentication is enabled. The keystore will be used as a truststore."); + // use keystore as truststore, as server needs to trust certificates signed by the server certificates + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(ks); + contextBuilder.clientAuth(ClientAuth.REQUIRE); + contextBuilder.trustManager(tmf); + } + + private SslProvider getSSLProvider() { + String providerName = props.getProperty(BrokerConstants.SSL_PROVIDER, SslProvider.JDK.name()); + try { + return SslProvider.valueOf(providerName); + } catch (IllegalArgumentException e) { + LOG.warn("unknown SSL Provider {}, falling back on JDK provider", providerName); + return SslProvider.JDK; + } + } + private InputStream jksDatastore(String jksPath) throws FileNotFoundException { URL jksUrl = getClass().getClassLoader().getResource(jksPath); if (jksUrl != null) { @@ -124,7 +182,6 @@ class DefaultMoquetteSslContextCreator implements ISslContextCreator { LOG.info("Loading external keystore. Url = {}.", jksFile.getAbsolutePath()); return new FileInputStream(jksFile); } - LOG.warn("The keystore file does not exist. Url = {}.", jksFile.getAbsolutePath()); - return null; + throw new FileNotFoundException("The keystore file does not exist. Url = " + jksFile.getAbsolutePath()); } } diff --git a/broker/src/main/java/io/moquette/server/netty/NettyAcceptor.java b/broker/src/main/java/io/moquette/server/netty/NettyAcceptor.java index 8777a633..424ed994 100644 --- a/broker/src/main/java/io/moquette/server/netty/NettyAcceptor.java +++ b/broker/src/main/java/io/moquette/server/netty/NettyAcceptor.java @@ -16,15 +16,33 @@ package io.moquette.server.netty; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + import io.moquette.BrokerConstants; import io.moquette.server.ServerAcceptor; import io.moquette.server.config.IConfig; -import io.moquette.server.netty.metrics.*; +import io.moquette.server.netty.metrics.BytesMetrics; +import io.moquette.server.netty.metrics.BytesMetricsCollector; +import io.moquette.server.netty.metrics.BytesMetricsHandler; +import io.moquette.server.netty.metrics.DropWizardMetricsHandler; +import io.moquette.server.netty.metrics.MQTTMessageLogger; +import io.moquette.server.netty.metrics.MessageMetrics; +import io.moquette.server.netty.metrics.MessageMetricsCollector; +import io.moquette.server.netty.metrics.MessageMetricsHandler; import io.moquette.spi.impl.ProtocolProcessor; import io.moquette.spi.security.ISslContextCreator; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBuf; -import io.netty.channel.*; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandler; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.EventLoopGroup; import io.netty.channel.epoll.EpollEventLoopGroup; import io.netty.channel.epoll.EpollServerSocketChannel; import io.netty.channel.nio.NioEventLoopGroup; @@ -41,17 +59,14 @@ import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; import io.netty.handler.codec.mqtt.MqttDecoder; import io.netty.handler.codec.mqtt.MqttEncoder; +import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslHandler; import io.netty.handler.timeout.IdleStateHandler; import io.netty.util.concurrent.Future; +import javax.net.ssl.SSLEngine; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLEngine; -import java.io.IOException; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.TimeUnit; + import static io.moquette.BrokerConstants.*; import static io.netty.channel.ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE; @@ -86,9 +101,9 @@ public class NettyAcceptor implements ServerAcceptor { } } - abstract static class PipelineInitializer { + private abstract static class PipelineInitializer { - abstract void init(ChannelPipeline pipeline) throws Exception; + abstract void init(SocketChannel channel) throws Exception; } private static final Logger LOG = LoggerFactory.getLogger(NettyAcceptor.class); @@ -119,7 +134,7 @@ public class NettyAcceptor implements ServerAcceptor { nettySoKeepalive = props.boolProp(BrokerConstants.NETTY_SO_KEEPALIVE_PROPERTY_NAME, true); nettyChannelTimeoutSeconds = props.intProp(BrokerConstants.NETTY_CHANNEL_TIMEOUT_SECONDS_PROPERTY_NAME, 10); maxBytesInMessage = props.intProp(BrokerConstants.NETTY_MAX_BYTES_PROPERTY_NAME, - BrokerConstants.DEFAULT_NETTY_MAX_BYTES_IN_MESSAGE); + BrokerConstants.DEFAULT_NETTY_MAX_BYTES_IN_MESSAGE); boolean epoll = props.boolProp(BrokerConstants.NETTY_EPOLL_PROPERTY_NAME, false); if (epoll) { @@ -158,7 +173,7 @@ public class NettyAcceptor implements ServerAcceptor { String sslTcpPortProp = props.getProperty(BrokerConstants.SSL_PORT_PROPERTY_NAME); String wssPortProp = props.getProperty(BrokerConstants.WSS_PORT_PROPERTY_NAME); if (sslTcpPortProp != null || wssPortProp != null) { - SSLContext sslContext = sslCtxCreator.initSSLContext(); + SslContext sslContext = sslCtxCreator.initSSLContext(); if (sslContext == null) { LOG.error("Can't initialize SSLHandler layer! Exiting, check your configuration of jks"); return; @@ -176,13 +191,7 @@ public class NettyAcceptor implements ServerAcceptor { @Override public void initChannel(SocketChannel ch) throws Exception { - ChannelPipeline pipeline = ch.pipeline(); - try { - pipeliner.init(pipeline); - } catch (Throwable th) { - LOG.error("Severe error during pipeline creation", th); - throw th; - } + pipeliner.init(ch); } }) .option(ChannelOption.SO_BACKLOG, nettySoBacklog) @@ -207,14 +216,15 @@ public class NettyAcceptor implements ServerAcceptor { String tcpPortProp = props.getProperty(PORT_PROPERTY_NAME, DISABLED_PORT_BIND); if (DISABLED_PORT_BIND.equals(tcpPortProp)) { LOG.info("Property {} has been set to {}. TCP MQTT will be disabled", BrokerConstants.PORT_PROPERTY_NAME, - DISABLED_PORT_BIND); + DISABLED_PORT_BIND); return; } int port = Integer.parseInt(tcpPortProp); initFactory(host, port, "TCP MQTT", new PipelineInitializer() { @Override - void init(ChannelPipeline pipeline) { + void init(SocketChannel channel) { + ChannelPipeline pipeline = channel.pipeline(); pipeline.addFirst("idleStateHandler", new IdleStateHandler(nettyChannelTimeoutSeconds, 0, 0)); pipeline.addAfter("idleStateHandler", "idleEventHandler", timeoutHandler); // pipeline.addLast("logger", new LoggingHandler("Netty", LogLevel.ERROR)); @@ -240,7 +250,7 @@ public class NettyAcceptor implements ServerAcceptor { if (DISABLED_PORT_BIND.equals(webSocketPortProp)) { // Do nothing no WebSocket configured LOG.info("Property {} has been setted to {}. Websocket MQTT will be disabled", - BrokerConstants.WEB_SOCKET_PORT_PROPERTY_NAME, DISABLED_PORT_BIND); + BrokerConstants.WEB_SOCKET_PORT_PROPERTY_NAME, DISABLED_PORT_BIND); return; } int port = Integer.parseInt(webSocketPortProp); @@ -251,7 +261,8 @@ public class NettyAcceptor implements ServerAcceptor { initFactory(host, port, "Websocket MQTT", new PipelineInitializer() { @Override - void init(ChannelPipeline pipeline) { + void init(SocketChannel channel) { + ChannelPipeline pipeline = channel.pipeline(); pipeline.addLast(new HttpServerCodec()); pipeline.addLast("aggregator", new HttpObjectAggregator(65536)); pipeline.addLast("webSocketHandler", @@ -270,13 +281,13 @@ public class NettyAcceptor implements ServerAcceptor { }); } - private void initializeSSLTCPTransport(NettyMQTTHandler handler, IConfig props, SSLContext sslContext) { + private void initializeSSLTCPTransport(NettyMQTTHandler handler, IConfig props, SslContext sslContext) { LOG.debug("Configuring SSL MQTT transport"); String sslPortProp = props.getProperty(SSL_PORT_PROPERTY_NAME, DISABLED_PORT_BIND); if (DISABLED_PORT_BIND.equals(sslPortProp)) { // Do nothing no SSL configured LOG.info("Property {} has been set to {}. SSL MQTT will be disabled", - BrokerConstants.SSL_PORT_PROPERTY_NAME, DISABLED_PORT_BIND); + BrokerConstants.SSL_PORT_PROPERTY_NAME, DISABLED_PORT_BIND); return; } @@ -290,8 +301,9 @@ public class NettyAcceptor implements ServerAcceptor { initFactory(host, sslPort, "SSL MQTT", new PipelineInitializer() { @Override - void init(ChannelPipeline pipeline) throws Exception { - pipeline.addLast("ssl", createSslHandler(sslContext, needsClientAuth)); + void init(SocketChannel channel) throws Exception { + ChannelPipeline pipeline = channel.pipeline(); + pipeline.addLast("ssl", createSslHandler(channel, sslContext, needsClientAuth)); pipeline.addFirst("idleStateHandler", new IdleStateHandler(nettyChannelTimeoutSeconds, 0, 0)); pipeline.addAfter("idleStateHandler", "idleEventHandler", timeoutHandler); // pipeline.addLast("logger", new LoggingHandler("Netty", LogLevel.ERROR)); @@ -305,13 +317,13 @@ public class NettyAcceptor implements ServerAcceptor { }); } - private void initializeWSSTransport(NettyMQTTHandler handler, IConfig props, SSLContext sslContext) { + private void initializeWSSTransport(NettyMQTTHandler handler, IConfig props, SslContext sslContext) { LOG.debug("Configuring secure websocket MQTT transport"); String sslPortProp = props.getProperty(WSS_PORT_PROPERTY_NAME, DISABLED_PORT_BIND); if (DISABLED_PORT_BIND.equals(sslPortProp)) { // Do nothing no SSL configured LOG.info("Property {} has been set to {}. Secure websocket MQTT will be disabled", - BrokerConstants.WSS_PORT_PROPERTY_NAME, DISABLED_PORT_BIND); + BrokerConstants.WSS_PORT_PROPERTY_NAME, DISABLED_PORT_BIND); return; } int sslPort = Integer.parseInt(sslPortProp); @@ -322,8 +334,9 @@ public class NettyAcceptor implements ServerAcceptor { initFactory(host, sslPort, "Secure websocket", new PipelineInitializer() { @Override - void init(ChannelPipeline pipeline) throws Exception { - pipeline.addLast("ssl", createSslHandler(sslContext, needsClientAuth)); + void init(SocketChannel channel) throws Exception { + ChannelPipeline pipeline = channel.pipeline(); + pipeline.addLast("ssl", createSslHandler(channel, sslContext, needsClientAuth)); pipeline.addLast("httpEncoder", new HttpResponseEncoder()); pipeline.addLast("httpDecoder", new HttpRequestDecoder()); pipeline.addLast("aggregator", new HttpObjectAggregator(65536)); @@ -379,11 +392,14 @@ public class NettyAcceptor implements ServerAcceptor { MessageMetrics metrics = m_metricsCollector.computeMetrics(); BytesMetrics bytesMetrics = m_bytesMetricsCollector.computeMetrics(); LOG.info("Metrics messages[read={}, write={}] bytes[read={}, write={}]", metrics.messagesRead(), - metrics.messagesWrote(), bytesMetrics.readBytes(), bytesMetrics.wroteBytes()); + metrics.messagesWrote(), bytesMetrics.readBytes(), bytesMetrics.wroteBytes()); } - private ChannelHandler createSslHandler(SSLContext sslContext, boolean needsClientAuth) { - SSLEngine sslEngine = sslContext.createSSLEngine(); + private ChannelHandler createSslHandler(SocketChannel channel, SslContext sslContext, boolean needsClientAuth) { + SSLEngine sslEngine = sslContext.newEngine( + channel.alloc(), + channel.remoteAddress().getHostString(), + channel.remoteAddress().getPort()); sslEngine.setUseClientMode(false); if (needsClientAuth) { sslEngine.setNeedClientAuth(true); diff --git a/broker/src/main/java/io/moquette/spi/security/ISslContextCreator.java b/broker/src/main/java/io/moquette/spi/security/ISslContextCreator.java index 5b8d6f76..85b93c82 100644 --- a/broker/src/main/java/io/moquette/spi/security/ISslContextCreator.java +++ b/broker/src/main/java/io/moquette/spi/security/ISslContextCreator.java @@ -16,12 +16,12 @@ package io.moquette.spi.security; -import javax.net.ssl.SSLContext; +import io.netty.handler.ssl.SslContext; /** * SSL certificate loader used to open SSL connections (websocket and MQTT-S). */ public interface ISslContextCreator { - SSLContext initSSLContext(); + SslContext initSSLContext(); } diff --git a/broker/src/test/java/io/moquette/server/ServerIntegrationOpenSSLTest.java b/broker/src/test/java/io/moquette/server/ServerIntegrationOpenSSLTest.java new file mode 100644 index 00000000..c547e671 --- /dev/null +++ b/broker/src/test/java/io/moquette/server/ServerIntegrationOpenSSLTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2012-2018 The original author or authors + * ------------------------------------------------------ + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * The Apache License v2.0 is available at + * http://www.opensource.org/licenses/apache2.0.php + * + * You may elect to redistribute this code under either of these licenses. + */ + +package io.moquette.server; + +import java.io.IOException; +import java.util.Properties; + +import io.moquette.BrokerConstants; +import io.netty.handler.ssl.OpenSsl; +import io.netty.handler.ssl.SslProvider; +import org.junit.BeforeClass; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Check that Moquette could also handle SSL with OpenSSL provider. + */ +public class ServerIntegrationOpenSSLTest extends ServerIntegrationSSLTest { + + private static final Logger LOG = LoggerFactory.getLogger(ServerIntegrationOpenSSLTest.class); + + @BeforeClass + public static void beforeTests() { + LOG.info("try to initialize OpenSSL native library"); + OpenSsl.ensureAvailability(); + LOG.info("OpenSSL initialized"); + } + + @Override + protected void startServer() throws IOException { + String file = getClass().getResource("/").getPath(); + System.setProperty("moquette.path", file); + m_server = new Server(); + Properties sslProps = new Properties(); + + sslProps.put(BrokerConstants.SSL_PROVIDER, SslProvider.OPENSSL.name()); + sslProps.put(BrokerConstants.NEED_CLIENT_AUTH, "true"); + + sslProps.put(BrokerConstants.SSL_PORT_PROPERTY_NAME, "8883"); + sslProps.put(BrokerConstants.JKS_PATH_PROPERTY_NAME, "serverkeystore.jks"); + sslProps.put(BrokerConstants.KEY_STORE_PASSWORD_PROPERTY_NAME, "passw0rdsrv"); + sslProps.put(BrokerConstants.KEY_MANAGER_PASSWORD_PROPERTY_NAME, "passw0rdsrv"); + sslProps.put(BrokerConstants.PERSISTENT_STORE_PROPERTY_NAME, IntegrationUtils.localMapDBPath()); + m_server.startServer(sslProps); + } +} diff --git a/broker/src/test/java/io/moquette/server/ServerLowlevelMessagesIntegrationTests.java b/broker/src/test/java/io/moquette/server/ServerLowlevelMessagesIntegrationTests.java index 00c8a2b8..d6e42e35 100644 --- a/broker/src/test/java/io/moquette/server/ServerLowlevelMessagesIntegrationTests.java +++ b/broker/src/test/java/io/moquette/server/ServerLowlevelMessagesIntegrationTests.java @@ -97,7 +97,8 @@ public class ServerLowlevelMessagesIntegrationTests { MqttConnectVariableHeader mqttConnectVariableHeader = new MqttConnectVariableHeader( MqttVersion.MQTT_3_1.protocolName(), MqttVersion.MQTT_3_1.protocolLevel(), false, false, false, 1, false, true, keepAlive); - MqttConnectPayload mqttConnectPayload = new MqttConnectPayload(clientID, (String)null, (byte[])null, (String)null, (byte[])null); + MqttConnectPayload mqttConnectPayload = new MqttConnectPayload(clientID, (String) null, (byte[]) null, + (String) null, (byte[]) null); return new MqttConnectMessage(mqttFixedHeader, mqttConnectVariableHeader, mqttConnectPayload); }