From f5a3d57afd7f9124b8dcdde22a43e5f06b7d6bbd Mon Sep 17 00:00:00 2001
From: Michael Konrad
Date: Wed, 17 Feb 2016 12:02:52 +0100
Subject: [PATCH] Enables client authentification for SSL
Client authentification can be set as with the SSL Properties to
require the client certificate to be signed by the server.
Added jUnit test and test certificates
---
.../java/io/moquette/BrokerConstants.java | 1 +
.../DefaultMoquetteSslContextCreator.java | 19 +-
.../moquette/server/netty/NettyAcceptor.java | 13 +-
.../ServerIntegrationSSLClientAuthTest.java | 201 ++++++++++++++++++
.../test/resources/signedclientkeystore.jks | Bin 0 -> 4503 bytes
.../test/resources/signedserverkeystore.jks | Bin 0 -> 4948 bytes
.../test/resources/unsignedclientkeystore.jks | Bin 0 -> 3691 bytes
7 files changed, 230 insertions(+), 4 deletions(-)
create mode 100644 broker/src/test/java/io/moquette/server/ServerIntegrationSSLClientAuthTest.java
create mode 100644 broker/src/test/resources/signedclientkeystore.jks
create mode 100644 broker/src/test/resources/signedserverkeystore.jks
create mode 100644 broker/src/test/resources/unsignedclientkeystore.jks
diff --git a/broker/src/main/java/io/moquette/BrokerConstants.java b/broker/src/main/java/io/moquette/BrokerConstants.java
index 062f79ef..63cac3e0 100644
--- a/broker/src/main/java/io/moquette/BrokerConstants.java
+++ b/broker/src/main/java/io/moquette/BrokerConstants.java
@@ -41,4 +41,5 @@ public class BrokerConstants {
public static final int PORT = 1883;
public static final int WEBSOCKET_PORT = 8080;
public static final String HOST = "0.0.0.0";
+ public static final String NEED_CLIENT_AUTH = "need_client_auth";
}
diff --git a/broker/src/main/java/io/moquette/server/DefaultMoquetteSslContextCreator.java b/broker/src/main/java/io/moquette/server/DefaultMoquetteSslContextCreator.java
index 97e790ea..4fab2b61 100644
--- a/broker/src/main/java/io/moquette/server/DefaultMoquetteSslContextCreator.java
+++ b/broker/src/main/java/io/moquette/server/DefaultMoquetteSslContextCreator.java
@@ -23,6 +23,9 @@ 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.*;
@@ -69,6 +72,11 @@ class DefaultMoquetteSslContextCreator implements ISslContextCreator {
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 {
InputStream jksInputStream = jksDatastore(jksPath);
SSLContext serverContext = SSLContext.getInstance("TLS");
@@ -76,7 +84,16 @@ class DefaultMoquetteSslContextCreator implements ISslContextCreator {
ks.load(jksInputStream, keyStorePassword.toCharArray());
final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, keyManagerPassword.toCharArray());
- serverContext.init(kmf.getKeyManagers(), null, null);
+ TrustManager[] trustManagers = null;
+ if (needsClientAuth) {
+ // 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();
+ }
+ // init sslContext
+ serverContext.init(kmf.getKeyManagers(), trustManagers, null);
+
return serverContext;
} catch (NoSuchAlgorithmException | UnrecoverableKeyException | CertificateException | KeyStoreException
| KeyManagementException | IOException ex) {
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 5609f530..54eaa575 100644
--- a/broker/src/main/java/io/moquette/server/netty/NettyAcceptor.java
+++ b/broker/src/main/java/io/moquette/server/netty/NettyAcceptor.java
@@ -205,10 +205,12 @@ public class NettyAcceptor implements ServerAcceptor {
final MoquetteIdleTimeoutHandler timeoutHandler = new MoquetteIdleTimeoutHandler();
String host = props.getProperty(BrokerConstants.HOST_PROPERTY_NAME);
+ String sNeedsClientAuth = props.getProperty(BrokerConstants.NEED_CLIENT_AUTH, "false");
+ final boolean needsClientAuth = Boolean.valueOf(sNeedsClientAuth);
initFactory(host, sslPort, new PipelineInitializer() {
@Override
void init(ChannelPipeline pipeline) throws Exception {
- pipeline.addLast("ssl", createSslHandler(sslContext));
+ pipeline.addLast("ssl", createSslHandler(sslContext, needsClientAuth));
pipeline.addFirst("idleStateHandler", new IdleStateHandler(0, 0, Constants.DEFAULT_CONNECT_TIMEOUT));
pipeline.addAfter("idleStateHandler", "idleEventHandler", timeoutHandler);
//pipeline.addLast("logger", new LoggingHandler("Netty", LogLevel.ERROR));
@@ -231,10 +233,12 @@ public class NettyAcceptor implements ServerAcceptor {
int sslPort = Integer.parseInt(sslPortProp);
final MoquetteIdleTimeoutHandler timeoutHandler = new MoquetteIdleTimeoutHandler();
String host = props.getProperty(BrokerConstants.HOST_PROPERTY_NAME);
+ String sNeedsClientAuth = props.getProperty(BrokerConstants.NEED_CLIENT_AUTH, "false");
+ final boolean needsClientAuth = Boolean.valueOf(sNeedsClientAuth);
initFactory(host, sslPort, new PipelineInitializer() {
@Override
void init(ChannelPipeline pipeline) throws Exception {
- pipeline.addLast("ssl", createSslHandler(sslContext));
+ pipeline.addLast("ssl", createSslHandler(sslContext, needsClientAuth));
pipeline.addLast("httpEncoder", new HttpResponseEncoder());
pipeline.addLast("httpDecoder", new HttpRequestDecoder());
pipeline.addLast("aggregator", new HttpObjectAggregator(65536));
@@ -281,9 +285,12 @@ public class NettyAcceptor implements ServerAcceptor {
LOG.info(String.format("Bytes read: %d, bytes wrote: %d", bytesMetrics.readBytes(), bytesMetrics.wroteBytes()));
}
- private ChannelHandler createSslHandler(SSLContext sslContext) {
+ private ChannelHandler createSslHandler(SSLContext sslContext, boolean needsClientAuth) {
SSLEngine sslEngine = sslContext.createSSLEngine();
sslEngine.setUseClientMode(false);
+ if(needsClientAuth) {
+ sslEngine.setNeedClientAuth(true);
+ }
return new SslHandler(sslEngine);
}
}
diff --git a/broker/src/test/java/io/moquette/server/ServerIntegrationSSLClientAuthTest.java b/broker/src/test/java/io/moquette/server/ServerIntegrationSSLClientAuthTest.java
new file mode 100644
index 00000000..e9ec48e0
--- /dev/null
+++ b/broker/src/test/java/io/moquette/server/ServerIntegrationSSLClientAuthTest.java
@@ -0,0 +1,201 @@
+/*
+ * 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 static org.junit.Assert.assertFalse;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.util.Properties;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import io.moquette.BrokerConstants;
+import org.eclipse.paho.client.mqttv3.IMqttClient;
+import org.eclipse.paho.client.mqttv3.MqttClient;
+import org.eclipse.paho.client.mqttv3.MqttClientPersistence;
+import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.persist.MqttDefaultFilePersistence;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Check that Moquette could also handle SSL with client authentification.
+ *
+ *
+ * Create certificates needed for client authentification
+ *
+ * # generate server certificate chain (valid for 10000 days)
+ * keytool -genkeypair -alias signedtestserver -dname cn=moquette.eclipse.org -keyalg RSA -keysize 2048 -keystore signedserverkeystore.jks -keypass passw0rdsrv -storepass passw0rdsrv -validity 10000
+ * keytool -genkeypair -alias signedtestserver_sub -dname cn=moquette.eclipse.org -keyalg RSA -keysize 2048 -keystore signedserverkeystore.jks -keypass passw0rdsrv -storepass passw0rdsrv
+ *
+ * # sign server subcertificate with server certificate (valid for 10000 days)
+ * keytool -certreq -alias signedtestserver_sub -keystore signedserverkeystore.jks -keypass passw0rdsrv -storepass passw0rdsrv | \
+ * keytool -gencert -alias signedtestserver -keystore signedserverkeystore.jks -keypass passw0rdsrv -storepass passw0rdsrv -validity 10000 | \
+ * keytool -importcert -alias signedtestserver_sub -keystore signedserverkeystore.jks -keypass passw0rdsrv -storepass passw0rdsrv
+ *
+ * # generate client keypair
+ * keytool -genkeypair -alias signedtestclient -dname cn=moquette.eclipse.org -keyalg RSA -keysize 2048 -keystore signedclientkeystore.jks -keypass passw0rd -storepass passw0rd
+ *
+ * # create signed client certificate with server subcertificate and import to client keystore (valid for 10000 days)
+ * keytool -certreq -alias signedtestclient -keystore signedclientkeystore.jks -keypass passw0rd -storepass passw0rd | \
+ * keytool -gencert -alias signedtestserver_sub -keystore signedserverkeystore.jks -keypass passw0rdsrv -storepass passw0rdsrv -validity 10000 | \
+ * keytool -importcert -alias signedtestclient -keystore signedclientkeystore.jks -keypass passw0rd -storepass passw0rd -noprompt
+ *
+ * # import server certificates into signed truststore
+ * keytool -exportcert -alias signedtestserver -keystore signedserverkeystore.jks -keypass passw0rdsrv -storepass passw0rdsrv | \
+ * keytool -importcert -trustcacerts -noprompt -alias signedtestserver -keystore signedclientkeystore.jks -keypass passw0rd -storepass passw0rd
+ * keytool -exportcert -alias signedtestserver_sub -keystore signedserverkeystore.jks -keypass passw0rdsrv -storepass passw0rdsrv | \
+ * keytool -importcert -trustcacerts -noprompt -alias signedtestserver_sub -keystore signedclientkeystore.jks -keypass passw0rd -storepass passw0rd
+ *
+ * # create unsigned client certificate (valid for 10000 days)
+ * keytool -genkeypair -alias unsignedtestclient -dname cn=moquette.eclipse.org -validity 10000 -keyalg RSA -keysize 2048 -keystore unsignedclientkeystore.jks -keypass passw0rd -storepass passw0rd
+ *
+ * # import server certificates into unsigned truststore
+ * keytool -exportcert -alias signedtestserver -keystore signedserverkeystore.jks -keypass passw0rdsrv -storepass passw0rdsrv | \
+ * keytool -importcert -trustcacerts -noprompt -alias signedtestserver -keystore unsignedclientkeystore.jks -keypass passw0rd -storepass passw0rd
+ * keytool -exportcert -alias signedtestserver_sub -keystore signedserverkeystore.jks -keypass passw0rdsrv -storepass passw0rdsrv | \
+ * keytool -importcert -trustcacerts -noprompt -alias signedtestserver_sub -keystore unsignedclientkeystore.jks -keypass passw0rd -storepass passw0rd
+ *
+ *
+ * @author andrea / konradm
+ */
+public class ServerIntegrationSSLClientAuthTest {
+ private static final Logger LOG = LoggerFactory.getLogger(ServerIntegrationSSLClientAuthTest.class);
+
+ Server m_server;
+ static MqttClientPersistence s_dataStore;
+
+ IMqttClient m_client;
+ TestCallback m_callback;
+
+ @BeforeClass
+ public static void beforeTests() {
+ String tmpDir = System.getProperty("java.io.tmpdir");
+ s_dataStore = new MqttDefaultFilePersistence(tmpDir);
+ }
+
+ 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_PORT_PROPERTY_NAME, "8883");
+ sslProps.put(BrokerConstants.JKS_PATH_PROPERTY_NAME, "signedserverkeystore.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());
+ sslProps.put(BrokerConstants.NEED_CLIENT_AUTH, "true");
+ m_server.startServer(sslProps);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ String dbPath = IntegrationUtils.localMapDBPath();
+ File dbFile = new File(dbPath);
+ assertFalse(String.format("The DB storagefile %s already exists", dbPath), dbFile.exists());
+
+ startServer();
+
+ m_client = new MqttClient("ssl://localhost:8883", "TestClient", s_dataStore);
+// m_client = new MqttClient("ssl://test.mosquitto.org:8883", "TestClient", s_dataStore);
+
+ m_callback = new TestCallback();
+ m_client.setCallback(m_callback);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (m_client != null && m_client.isConnected()) {
+ m_client.disconnect();
+ }
+
+ if (m_server != null) {
+ m_server.stopServer();
+ }
+ String dbPath = IntegrationUtils.localMapDBPath();
+ File dbFile = new File(dbPath);
+ if (dbFile.exists()) {
+ dbFile.delete();
+ }
+ assertFalse(dbFile.exists());
+ }
+
+ @Test
+ public void checkClientAuthentification() throws Exception {
+ LOG.info("*** checkClientAuthentification ***");
+ SSLSocketFactory ssf = configureSSLSocketFactory("signedclientkeystore.jks");
+
+ MqttConnectOptions options = new MqttConnectOptions();
+ options.setSocketFactory(ssf);
+ m_client.connect(options);
+ m_client.subscribe("/topic", 0);
+ m_client.disconnect();
+ }
+
+ @Test (expected = MqttException.class)
+ public void checkClientAuthentificationFail() throws Exception {
+ LOG.info("*** checkClientAuthentificationFail ***");
+ SSLSocketFactory ssf = configureSSLSocketFactory("unsignedclientkeystore.jks");
+
+ MqttConnectOptions options = new MqttConnectOptions();
+ options.setSocketFactory(ssf);
+ // actual a "Broken pipe" is thrown, this is not very specific.
+ try {
+ m_client.connect(options);
+ }catch(MqttException e)
+ {
+ e.printStackTrace();
+ throw e;
+ }
+ }
+
+ private SSLSocketFactory configureSSLSocketFactory(String keystore) throws KeyManagementException, NoSuchAlgorithmException, UnrecoverableKeyException, IOException, CertificateException, KeyStoreException {
+ KeyStore ks = KeyStore.getInstance("JKS");
+ InputStream jksInputStream = getClass().getClassLoader().getResourceAsStream(keystore);
+ ks.load(jksInputStream, "passw0rd".toCharArray());
+
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+ kmf.init(ks, "passw0rd".toCharArray());
+
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ tmf.init(ks);
+
+ SSLContext sc = SSLContext.getInstance("TLS");
+ TrustManager[] trustManagers = tmf.getTrustManagers();
+ sc.init(kmf.getKeyManagers(), trustManagers, null);
+
+ SSLSocketFactory ssf = sc.getSocketFactory();
+ return ssf;
+ }
+}
diff --git a/broker/src/test/resources/signedclientkeystore.jks b/broker/src/test/resources/signedclientkeystore.jks
new file mode 100644
index 0000000000000000000000000000000000000000..42cc40378c40070791838bb5d4440638261c27f8
GIT binary patch
literal 4503
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
literal 0
HcmV?d00001
diff --git a/broker/src/test/resources/unsignedclientkeystore.jks b/broker/src/test/resources/unsignedclientkeystore.jks
new file mode 100644
index 0000000000000000000000000000000000000000..3495c3ffa2e0ac666b4c09532408bde4e2ed94cf
GIT binary patch
literal 3691
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