From 51627ea2f685a16f97f18c0f45b7d6290996eb53 Mon Sep 17 00:00:00 2001 From: andsel Date: Sun, 13 Dec 2015 16:26:12 +0100 Subject: [PATCH] Added ISslContextCreator method argument to Server.startServer to use embedded SSL certificates (fix issue #115) --- .gitignore | 2 + .../DefaultMoquetteSslContextCreator.java | 102 ++++++++++++++++ .../moquette/server/ISslContextCreator.java | 28 +++++ .../main/java/io/moquette/server/Server.java | 13 +- .../io/moquette/server/ServerAcceptor.java | 2 +- .../moquette/server/netty/NettyAcceptor.java | 115 +++--------------- 6 files changed, 163 insertions(+), 99 deletions(-) create mode 100644 broker/src/main/java/io/moquette/server/DefaultMoquetteSslContextCreator.java create mode 100644 broker/src/main/java/io/moquette/server/ISslContextCreator.java diff --git a/.gitignore b/.gitignore index d27251ea..e148fcc9 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,5 @@ broker/runner hs_err_pid* broker/moquette_store.* embedding_moquette/target/ +broker/nb-configuration.xml +broker/nbactions-Server.xml diff --git a/broker/src/main/java/io/moquette/server/DefaultMoquetteSslContextCreator.java b/broker/src/main/java/io/moquette/server/DefaultMoquetteSslContextCreator.java new file mode 100644 index 00000000..15e07424 --- /dev/null +++ b/broker/src/main/java/io/moquette/server/DefaultMoquetteSslContextCreator.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2012-2015 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 io.moquette.BrokerConstants; +import io.moquette.server.config.IConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +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 config file. + * + * Created by andrea on 13/12/15. + */ +class DefaultMoquetteSslContextCreator implements ISslContextCreator { + + private static final Logger LOG = LoggerFactory.getLogger(DefaultMoquetteSslContextCreator.class); + + private IConfig props; + + public DefaultMoquetteSslContextCreator(IConfig props) { + this.props = props; + } + + @Override + public SSLContext initSSLContext() { + final String jksPath = props.getProperty(BrokerConstants.JKS_PATH_PROPERTY_NAME); + LOG.info("Starting SSL using keystore at {}", jksPath); + if (jksPath == null || jksPath.isEmpty()) { + //key_store_password or key_manager_password are empty + LOG.warn("You have configured the SSL port but not the jks_path, SSL not started"); + 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("You have configured the SSL port but not the key_store_password, SSL not started"); + return null; + } + if (keyManagerPassword == null || keyManagerPassword.isEmpty()) { + //key_manager_password or key_manager_password are empty + LOG.warn("You have configured the SSL port but not the key_manager_password, SSL not started"); + return null; + } + + try { + InputStream jksInputStream = jksDatastore(jksPath); + SSLContext serverContext = SSLContext.getInstance("TLS"); + final KeyStore ks = KeyStore.getInstance("JKS"); + ks.load(jksInputStream, keyStorePassword.toCharArray()); + final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(ks, keyManagerPassword.toCharArray()); + serverContext.init(kmf.getKeyManagers(), null, null); + return serverContext; + } catch (NoSuchAlgorithmException | UnrecoverableKeyException | CertificateException | KeyStoreException + | KeyManagementException | IOException ex) { + LOG.error("Can't start SSL layer!", ex); + return null; + } + } + + private InputStream jksDatastore(String jksPath) throws FileNotFoundException { + URL jksUrl = getClass().getClassLoader().getResource(jksPath); + if (jksUrl != null) { + LOG.info("Starting with jks at {}, jks normal {}", jksUrl.toExternalForm(), jksUrl); + return getClass().getClassLoader().getResourceAsStream(jksPath); + } + LOG.info("jks not found in bundled resources, try on the filesystem"); + File jksFile = new File(jksPath); + if (jksFile.exists()) { + LOG.info("Using {} ", jksFile.getAbsolutePath()); + return new FileInputStream(jksFile); + } + LOG.warn("File {} doesn't exists", jksFile.getAbsolutePath()); + return null; + } +} diff --git a/broker/src/main/java/io/moquette/server/ISslContextCreator.java b/broker/src/main/java/io/moquette/server/ISslContextCreator.java new file mode 100644 index 00000000..6b40b03b --- /dev/null +++ b/broker/src/main/java/io/moquette/server/ISslContextCreator.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2012-2015 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 javax.net.ssl.SSLContext; + +/** + * SSL certificate loader used to open SSL connections (websocket and MQTT-S). + * + * Created by andrea on 13/12/15. + */ +public interface ISslContextCreator { + + SSLContext initSSLContext(); +} diff --git a/broker/src/main/java/io/moquette/server/Server.java b/broker/src/main/java/io/moquette/server/Server.java index 9253eab6..e055a5c4 100644 --- a/broker/src/main/java/io/moquette/server/Server.java +++ b/broker/src/main/java/io/moquette/server/Server.java @@ -100,9 +100,14 @@ public class Server { } /** - * Starts Moquette with config provided by an implementation of IConfig class and with the set of InterceptHandler + * Starts Moquette with config provided by an implementation of IConfig class and with the + * set of InterceptHandler. * */ public void startServer(IConfig config, List handlers) throws IOException { + startServer(config, handlers, null); + } + + public void startServer(IConfig config, List handlers, ISslContextCreator sslCtxCreator) throws IOException { if (handlers == null) { handlers = Collections.emptyList(); } @@ -114,8 +119,12 @@ public class Server { LOG.info("Persistent store file: " + config.getProperty(BrokerConstants.PERSISTENT_STORE_PROPERTY_NAME)); final ProtocolProcessor processor = SimpleMessaging.getInstance().init(config, handlers); + if (sslCtxCreator == null) { + sslCtxCreator = new DefaultMoquetteSslContextCreator(config); + } + m_acceptor = new NettyAcceptor(); - m_acceptor.initialize(processor, config); + m_acceptor.initialize(processor, config, sslCtxCreator); m_processor = processor; m_initialized = true; } diff --git a/broker/src/main/java/io/moquette/server/ServerAcceptor.java b/broker/src/main/java/io/moquette/server/ServerAcceptor.java index 9532415e..b21bf638 100644 --- a/broker/src/main/java/io/moquette/server/ServerAcceptor.java +++ b/broker/src/main/java/io/moquette/server/ServerAcceptor.java @@ -26,7 +26,7 @@ import io.moquette.spi.impl.ProtocolProcessor; */ public interface ServerAcceptor { - void initialize(ProtocolProcessor processor, IConfig props) throws IOException; + void initialize(ProtocolProcessor processor, IConfig props, ISslContextCreator sslCtxCreator) throws IOException; void close(); } 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 e3ab6e94..a7d015b0 100644 --- a/broker/src/main/java/io/moquette/server/netty/NettyAcceptor.java +++ b/broker/src/main/java/io/moquette/server/netty/NettyAcceptor.java @@ -18,8 +18,13 @@ package io.moquette.server.netty; import io.moquette.BrokerConstants; import io.moquette.commons.Constants; import io.moquette.parser.netty.MQTTDecoder; +import io.moquette.parser.netty.MQTTEncoder; +import io.moquette.server.DefaultMoquetteSslContextCreator; +import io.moquette.server.ISslContextCreator; +import io.moquette.server.ServerAcceptor; import io.moquette.server.config.IConfig; import io.moquette.server.netty.metrics.*; +import io.moquette.spi.impl.ProtocolProcessor; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBuf; import io.netty.channel.*; @@ -36,19 +41,12 @@ import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; import io.netty.handler.ssl.SslHandler; import io.netty.handler.timeout.IdleStateHandler; import io.netty.util.concurrent.Future; -import io.moquette.parser.netty.MQTTEncoder; -import io.moquette.server.ServerAcceptor; -import io.moquette.spi.impl.ProtocolProcessor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; -import java.io.*; -import java.net.URL; -import java.security.*; -import java.security.cert.CertificateException; +import java.io.IOException; import java.util.List; /** @@ -94,7 +92,7 @@ public class NettyAcceptor implements ServerAcceptor { MessageMetricsCollector m_metricsCollector = new MessageMetricsCollector(); @Override - public void initialize(ProtocolProcessor processor, IConfig props) throws IOException { + public void initialize(ProtocolProcessor processor, IConfig props, ISslContextCreator sslCtxCreator) throws IOException { m_bossGroup = new NioEventLoopGroup(); m_workerGroup = new NioEventLoopGroup(); final NettyMQTTHandler handler = new NettyMQTTHandler(processor); @@ -104,13 +102,13 @@ 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) { - SslHandlerFactory sslHandlerFactory = initSSLHandlerFactory(props); - if (!sslHandlerFactory.canCreate()) { + SSLContext sslContext = sslCtxCreator.initSSLContext(); + if (sslContext == null) { LOG.error("Can't initialize SSLHandler layer! Exiting, check your configuration of jks"); return; } - initializeSSLTCPTransport(handler, props, sslHandlerFactory); - initializeWSSTransport(handler, props, sslHandlerFactory); + initializeSSLTCPTransport(handler, props, sslContext); + initializeWSSTransport(handler, props, sslContext); } } @@ -195,7 +193,7 @@ public class NettyAcceptor implements ServerAcceptor { }); } - private void initializeSSLTCPTransport(final NettyMQTTHandler handler, IConfig props, final SslHandlerFactory sslHandlerFactory) throws IOException { + private void initializeSSLTCPTransport(final NettyMQTTHandler handler, IConfig props, final SSLContext sslContext) throws IOException { String sslPortProp = props.getProperty(BrokerConstants.SSL_PORT_PROPERTY_NAME); if (sslPortProp == null) { //Do nothing no SSL configured @@ -211,7 +209,7 @@ public class NettyAcceptor implements ServerAcceptor { initFactory(host, sslPort, new PipelineInitializer() { @Override void init(ChannelPipeline pipeline) throws Exception { - pipeline.addLast("ssl", sslHandlerFactory.create()); + pipeline.addLast("ssl", createSslHandler(sslContext)); pipeline.addFirst("idleStateHandler", new IdleStateHandler(0, 0, Constants.DEFAULT_CONNECT_TIMEOUT)); pipeline.addAfter("idleStateHandler", "idleEventHandler", timeoutHandler); //pipeline.addLast("logger", new LoggingHandler("Netty", LogLevel.ERROR)); @@ -224,7 +222,7 @@ public class NettyAcceptor implements ServerAcceptor { }); } - private void initializeWSSTransport(final NettyMQTTHandler handler, IConfig props, final SslHandlerFactory sslHandlerFactory) throws IOException { + private void initializeWSSTransport(final NettyMQTTHandler handler, IConfig props, final SSLContext sslContext) throws IOException { String sslPortProp = props.getProperty(BrokerConstants.WSS_PORT_PROPERTY_NAME); if (sslPortProp == null) { //Do nothing no SSL configured @@ -237,7 +235,7 @@ public class NettyAcceptor implements ServerAcceptor { initFactory(host, sslPort, new PipelineInitializer() { @Override void init(ChannelPipeline pipeline) throws Exception { - pipeline.addLast("ssl", sslHandlerFactory.create()); + pipeline.addLast("ssl", createSslHandler(sslContext)); pipeline.addLast("httpEncoder", new HttpResponseEncoder()); pipeline.addLast("httpDecoder", new HttpRequestDecoder()); pipeline.addLast("aggregator", new HttpObjectAggregator(65536)); @@ -284,84 +282,9 @@ public class NettyAcceptor implements ServerAcceptor { LOG.info(String.format("Bytes read: %d, bytes wrote: %d", bytesMetrics.readBytes(), bytesMetrics.wroteBytes())); } - - private SslHandlerFactory initSSLHandlerFactory(IConfig props) { - SslHandlerFactory factory = new SslHandlerFactory(props); - return factory.canCreate() ? factory : null; - } - - private static class SslHandlerFactory { - - private SSLContext sslContext; - - public SslHandlerFactory(IConfig props) { - this.sslContext = initSSLContext(props); - } - - public boolean canCreate() { - return this.sslContext != null; - } - private SSLContext initSSLContext(IConfig props) { - final String jksPath = props.getProperty(BrokerConstants.JKS_PATH_PROPERTY_NAME); - LOG.info("Starting SSL using keystore at {}", jksPath); - if (jksPath == null || jksPath.isEmpty()) { - //key_store_password or key_manager_password are empty - LOG.warn("You have configured the SSL port but not the jks_path, SSL not started"); - 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("You have configured the SSL port but not the key_store_password, SSL not started"); - return null; - } - if (keyManagerPassword == null || keyManagerPassword.isEmpty()) { - //key_manager_password or key_manager_password are empty - LOG.warn("You have configured the SSL port but not the key_manager_password, SSL not started"); - return null; - } - - try { - InputStream jksInputStream = jksDatastore(jksPath); - SSLContext serverContext = SSLContext.getInstance("TLS"); - final KeyStore ks = KeyStore.getInstance("JKS"); - ks.load(jksInputStream, keyStorePassword.toCharArray()); - final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - kmf.init(ks, keyManagerPassword.toCharArray()); - serverContext.init(kmf.getKeyManagers(), null, null); - return serverContext; - } catch (NoSuchAlgorithmException | UnrecoverableKeyException | CertificateException | KeyStoreException - | KeyManagementException | IOException ex) { - LOG.error("Can't start SSL layer!", ex); - return null; - } - } - - private InputStream jksDatastore(String jksPath) throws FileNotFoundException { - URL jksUrl = getClass().getClassLoader().getResource(jksPath); - if (jksUrl != null) { - LOG.info("Starting with jks at {}, jks normal {}", jksUrl.toExternalForm(), jksUrl); - return getClass().getClassLoader().getResourceAsStream(jksPath); - } - LOG.info("jks not found in bundled resources, try on the filesystem"); - File jksFile = new File(jksPath); - if (jksFile.exists()) { - LOG.info("Using {} ", jksFile.getAbsolutePath()); - return new FileInputStream(jksFile); - } - LOG.warn("File {} doesn't exists", jksFile.getAbsolutePath()); - return null; - } - - public ChannelHandler create() { - SSLEngine sslEngine = sslContext.createSSLEngine(); - sslEngine.setUseClientMode(false); - return new SslHandler(sslEngine); - } - + private ChannelHandler createSslHandler(SSLContext sslContext) { + SSLEngine sslEngine = sslContext.createSSLEngine(); + sslEngine.setUseClientMode(false); + return new SslHandler(sslEngine); } }