Added ISslContextCreator method argument to Server.startServer to use embedded SSL certificates (fix issue #115)
This commit is contained in:
@@ -36,3 +36,5 @@ broker/runner
|
||||
hs_err_pid*
|
||||
broker/moquette_store.*
|
||||
embedding_moquette/target/
|
||||
broker/nb-configuration.xml
|
||||
broker/nbactions-Server.xml
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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<? extends InterceptHandler> handlers) throws IOException {
|
||||
startServer(config, handlers, null);
|
||||
}
|
||||
|
||||
public void startServer(IConfig config, List<? extends InterceptHandler> 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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
private ChannelHandler createSslHandler(SSLContext sslContext) {
|
||||
SSLEngine sslEngine = sslContext.createSSLEngine();
|
||||
sslEngine.setUseClientMode(false);
|
||||
return new SslHandler(sslEngine);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user