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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