maydemirx / moquette-mqtt

Automatically exported from code.google.com/p/moquette-mqtt
Apache License 2.0
0 stars 1 forks source link

SSL support #41

Closed GoogleCodeExporter closed 8 years ago

GoogleCodeExporter commented 8 years ago
Allow clients to connect through encrypted connections.

Original issue reported on code.google.com by davidmen...@gmail.com on 21 Apr 2014 at 12:22

GoogleCodeExporter commented 8 years ago
I have added SSL with a couple of changes to the code. 

In the NettyAcceptor I have added the SSL Handler as the first of the last 
handlers. The context in order to create it comes from a helper class that 
loads the certificates and key from the paths specified on mosquette.conf.

It obviously needs some work yet, but with these changes it already works.

NettyAceptor.java
***
public void initialize(IMessaging messaging, final Properties props)
        throws IOException {
    m_bossGroup = new NioEventLoopGroup();
    m_workerGroup = new NioEventLoopGroup();

    final NettyMQTTHandler handler = new NettyMQTTHandler();
    handler.setMessaging(messaging);

    ServerBootstrap b = new ServerBootstrap();
    b.group(m_bossGroup, m_workerGroup)
            .channel(NioServerSocketChannel.class)
            .childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) throws Exception {
                    ChannelPipeline pipeline = ch.pipeline();

                    SSLContext sslContext = SSLContextFactory
                            .getSSLContext(props);
                    SSLEngine engine = sslContext.createSSLEngine();
                    engine.setUseClientMode(false);
                    engine.setEnableSessionCreation(true);
                    engine.setWantClientAuth(true);

                    // pipeline.addFirst("metrics", new
                    // BytesMetricsHandler(m_metricsCollector));
                    pipeline.addFirst("idleStateHandler",
                            new IdleStateHandler(0, 0,
                                    Constants.DEFAULT_CONNECT_TIMEOUT));
                    pipeline.addAfter("idleStateHandler",
                            "idleEventHandler",
                            new MoquetteIdleTimoutHandler());
                    // pipeline.addLast("logger", new
                    // LoggingHandler("Netty", LogLevel.ERROR));

                    SslHandler sslHandler = new SslHandler(engine);

                    pipeline.addLast("ssl", sslHandler);
                    pipeline.addLast("decoder", new MQTTDecoder());
                    pipeline.addLast("encoder", new MQTTEncoder());
                    pipeline.addLast("metrics", new MessageMetricsHandler(
                            m_metricsCollector));

                    pipeline.addLast("handler", handler);

                }
            }).option(ChannelOption.SO_BACKLOG, 128)
            .option(ChannelOption.SO_REUSEADDR, true)
            .childOption(ChannelOption.SO_KEEPALIVE, true);
    try {
        // Bind and start to accept incoming connections.
        // ChannelFuture f = b.bind(Constants.PORT);
        ChannelFuture f = b.bind(props.getProperty("host"),
                Integer.parseInt(props.getProperty("port")));
        LOG.info("Server binded");
        f.sync();
    } catch (InterruptedException ex) {
        LOG.error(null, ex);
    }
}
***

SSLContextFactory.java

package org.dna.mqtt.moquette.server.netty;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.InputStream;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.Security;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Properties;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMDecryptorProvider;
import org.bouncycastle.openssl.PEMEncryptedKeyPair;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;

public class SSLContextFactory {

    public static SSLContext getSSLContext(Properties props) throws Exception {

        String caCrtFile = props.getProperty("cafile");
        String crtFile = props.getProperty("certfile");
        String keyFile = props.getProperty("keyfile");
        String password = props.getProperty("keypassphrase");

        Security.addProvider(new BouncyCastleProvider());

        CertificateFactory cf = CertificateFactory.getInstance("X.509");

        String configPath = System.getProperty("moquette.path", "");

        InputStream caCertIS = new FileInputStream(new File(configPath + caCrtFile));
        X509Certificate caCert = (X509Certificate) cf
                .generateCertificate(caCertIS);

        InputStream crtFileIS = new FileInputStream(new File(configPath + crtFile));
        X509Certificate cert = (X509Certificate) cf
                .generateCertificate(crtFileIS);

        File privateKeyFile = new File(configPath + keyFile);
        PEMParser pemParser = new PEMParser(new FileReader(privateKeyFile));
        PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder()
                .build(password.toCharArray());
        ;
        JcaPEMKeyConverter converter = new JcaPEMKeyConverter()
                .setProvider("BC");

        Object object = pemParser.readObject();
        KeyPair kp;

        if (object instanceof PEMEncryptedKeyPair) {
            kp = converter.getKeyPair(((PEMEncryptedKeyPair) object)
                    .decryptKeyPair(decProv));
        } else {
            kp = converter.getKeyPair((PEMKeyPair) object);
        }

        pemParser.close();

        KeyStore caKs = KeyStore.getInstance(KeyStore.getDefaultType());
        caKs.load(null, null);
        caKs.setCertificateEntry("ca-certificate", caCert);
        TrustManagerFactory tmf = TrustManagerFactory
                .getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(caKs);

        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
        ks.load(null, null);
        ks.setCertificateEntry("certificate", cert);
        ks.setKeyEntry("private-key", kp.getPrivate(), password.toCharArray(),
                new java.security.cert.Certificate[] { cert });
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory
                .getDefaultAlgorithm());
        kmf.init(ks, password.toCharArray());

        SSLContext context = SSLContext.getInstance("TLSv1");
        context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

        return context;

    }

}

pom.xml

***
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.48</version>
</dependency>
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcpkix-jdk15on</artifactId>
    <version>1.48</version>
</dependency>
***

mosquette.conf

##############################################
#  Moquette configuration file. 
#
#  The synthax is equals to mosquitto.conf
# 
##############################################

port 1883

host 0.0.0.0

password_file config/password_file.conf

cafile config/m2mqtt_ca.crt

certfile config/m2mqtt_srv.crt

keyfile config/m2mqtt_srv.key

keypassphrase ******

Original comment by davidmen...@gmail.com on 24 Apr 2014 at 12:14

GoogleCodeExporter commented 8 years ago
Integrated with r=775acff84631ddd74fb26c9709ebbb41e44308ea

Thanks a lot for the suggestion! 
Just one question why you preferred BouncyCastle instead of plain JKS?

 Andrea

Original comment by selva.an...@gmail.com on 22 May 2014 at 10:16