MQTT安全:使用ssl实现EMQ与android客户端安全通信

原文地址

使用 SSL 对 MQTT的消息交换进行加密,提高安全性。

  • 服务端使用的是 EMQ v2.0
  • 客户端使用的是 Eclipse Paho Android Service

服务器启用SSL

我们需要数字证书来对进行ssl通信用户进行强认证。由于获得一个真正受外界信任的证书需要花费money,所有我们采用自签名证书。

要实现双向认证(服务器认证客户端、客户端认证服务器)我们需要3个证书,一个CA证书,一个EMQ服务器证书,一个客户端证书。

具体的生成证书操作、启用EMQ ssl和双向认证,参考这篇文章 Securing EMQ Connections with SSL

android客户端的ssl实现

上一步我们生成了客户端使用的证书 MyClient1.pem 和私有秘钥MyClient1.key,但是要想在android上使用需要将其转成bks格式。

pem 转 bks

1、首先生成.p12文件:

openssl pkcs12 -export -nodes -in MyClient1.pem -inkey MyClient1.key -out client.p12
  • -inkey为私钥文件
  • -in为证书,如果pem私钥没有密码,则使用-nodes表示无密码,如果有密码使用-passin;如果私钥和证书都在同一文件里则-in-inkey指定同一个文件。

会提示输入给.p12秘钥库设置的密码,请记住,下面会用到

2、生成.bks证书:

keytool -importkeystore -srckeystore client.p12 -srcstoretype pkcs12 -destkeystore client.bks -deststoretype bks -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath bcprov-ext-jdk15on-158.jar 

使用 KeyTool 转换为 BKS 格式时,需要 bcprov-ext-jdk15on-158.jar,可以在 这里 找到。文件路径直接带在-providerpath 参数后面即可。也可以把jar包放到如下路径:jdk/jre/lib/ext,从而省略-providerpath

会首先提示输入给bks秘钥库设置的密码,请记住,下面会用到。

然后会提示输入p12秘钥库密码,即上一步设置的密码。

3、查看bks证书库列表进行验证

keytool -list -rfc -keystore client.bks -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider -storepass 'bks秘钥库密码'

源码

完整源码见 GitHub

主要代码

package paho.android.mqtt_example;

import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.security.cert.CertificateException;
import timber.log.Timber;

/**
 * Original SocketFactory file taken from https://github.com/owntracks/android
 */

public class SelfSignedSocketFactory extends javax.net.ssl.SSLSocketFactory {
    private javax.net.ssl.SSLSocketFactory factory;


    public static class SocketFactoryOptions {

        private InputStream caCrtInputStream;
        private InputStream caClientBksInputStream;
        private String caClientBksPassword;


        /**
         *
         * @param stream the self-signed Root CA Certificate's stream
         * @return
         */
        public SocketFactoryOptions withCaInputStream(InputStream stream) {
            this.caCrtInputStream = stream;
            return this;
        }


        /**
         *
         * @param stream the self-signed client Certificate's stream .
         * @return
         */
        public SocketFactoryOptions withClientBksInputStream(InputStream stream) {
            this.caClientBksInputStream = stream;
            return this;
        }


        public SocketFactoryOptions withClientBksPassword(String password) {
            this.caClientBksPassword = password;
            return this;
        }


        public boolean hasCaCrt() {
            return caCrtInputStream != null;
        }


        public boolean hasClientBksCrt() {
            return caClientBksPassword != null;
        }


        public InputStream getCaCrtInputStream() {
            return caCrtInputStream;
        }


        public InputStream getCaClientBksInputStream() {
            return caClientBksInputStream;
        }


        public String getCaClientBksPassword() {
            return caClientBksPassword;
        }


        public boolean hasClientBksPassword() {
            return (caClientBksPassword != null) && !caClientBksPassword.equals("");
        }
    }


    public SelfSignedSocketFactory()
        throws CertificateException, KeyStoreException, NoSuchAlgorithmException, IOException, KeyManagementException,
               java.security.cert.CertificateException, UnrecoverableKeyException {
        this(new SocketFactoryOptions());
    }


    private TrustManagerFactory tmf;


    public SelfSignedSocketFactory(SocketFactoryOptions options)
        throws KeyStoreException, NoSuchAlgorithmException, IOException, KeyManagementException,
               java.security.cert.CertificateException, UnrecoverableKeyException {
        Log.v(this.toString(), "initializing CustomSocketFactory");

        tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");

        if (options.hasCaCrt()) {
            Log.v(this.toString(), "MQTT_CONNECTION_OPTIONS.hasCaCrt(): true");
            // CA certificate is used to authenticate server
            CertificateFactory cAf = CertificateFactory.getInstance("X.509");
            X509Certificate ca = (X509Certificate) cAf.generateCertificate(options.getCaCrtInputStream());
            KeyStore caKs = KeyStore.getInstance(KeyStore.getDefaultType());
            caKs.load(null, null);
            caKs.setCertificateEntry("ca-certificate", ca);
            tmf.init(caKs);
        } else {
            Timber.v("CA sideload: false, using system keystore");
            KeyStore keyStore = KeyStore.getInstance("AndroidCAStore");
            keyStore.load(null);
            tmf.init(keyStore);
        }

        if (options.hasClientBksCrt()) {
            Log.v(this.toString(), "MQTT_CONNECTION_OPTIONS.hasClientBksCrt(): true");

            // init client key store
            KeyStore clientkeyStore = KeyStore.getInstance("BKS");
            clientkeyStore.load(options.getCaClientBksInputStream(),
                options.hasClientBksPassword() ? options.getCaClientBksPassword().toCharArray() : new char[0]);
            kmf.init(clientkeyStore,
                options.hasClientBksPassword() ? options.getCaClientBksPassword().toCharArray() : new char[0]);

        } else {
            Log.v(this.toString(), "Client .bks sideload: false, using null CLIENT cert");
            kmf.init(null, null);
        }

        // Create an SSLContext that uses our TrustManager
        SSLContext context = SSLContext.getInstance("TLSv1.2");
        context.init(kmf.getKeyManagers(), getTrustManagers(), null);
        this.factory = context.getSocketFactory();

    }


    public TrustManager[] getTrustManagers() {
        return tmf.getTrustManagers();
    }


    @Override
    public String[] getDefaultCipherSuites() {
        return this.factory.getDefaultCipherSuites();
    }


    @Override
    public String[] getSupportedCipherSuites() {
        return this.factory.getSupportedCipherSuites();
    }


    @Override
    public Socket createSocket() throws IOException {
        SSLSocket r = (SSLSocket) this.factory.createSocket();
        r.setEnabledProtocols(new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" });
        return r;
    }


    @Override
    public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
        SSLSocket r = (SSLSocket) this.factory.createSocket(s, host, port, autoClose);
        r.setEnabledProtocols(new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" });
        return r;
    }


    @Override
    public Socket createSocket(String host, int port) throws IOException {

        SSLSocket r = (SSLSocket) this.factory.createSocket(host, port);
        r.setEnabledProtocols(new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" });
        return r;
    }


    @Override
    public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
        SSLSocket r = (SSLSocket) this.factory.createSocket(host, port, localHost, localPort);
        r.setEnabledProtocols(new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" });
        return r;
    }


    @Override
    public Socket createSocket(InetAddress host, int port) throws IOException {
        SSLSocket r = (SSLSocket) this.factory.createSocket(host, port);
        r.setEnabledProtocols(new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" });
        return r;
    }


    @Override
    public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort)
        throws IOException {
        SSLSocket r = (SSLSocket) this.factory.createSocket(address, port, localAddress, localPort);
        r.setEnabledProtocols(new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" });
        return r;
    }
}

你可能感兴趣的:(MQTT安全:使用ssl实现EMQ与android客户端安全通信)