如何使用自签名证书连接到 Android(paho 客户端)中的 Mqtt 服务器?
How to use a self signed certificate to connect to a Mqtt server in Android (paho client)?
我在使用自签名证书连接到 mqtt 服务器时遇到问题。我正在使用 Paho 客户端并希望使用 TLSv1.2
连接到服务器。实际上,我在 Android API 20+ 中成功连接,但低于此版本没有成功。
到目前为止我做了什么:
1- 创建了一个 PKCS#12 密钥库并放入 .crt 文件并为其分配密码并保存(它将是一个 .pfx 文件)
2- 将 .pfx 文件添加到 android 项目
的资源原始文件夹中
3- 使用以下代码加载自签名证书:
connection = createConnection(mqttCallback);
MqttConnectOptions connOpts = optionsFromModel(connectionModel);
connOpts.setSocketFactory(getSSLSocketFactory(keyStoreInputStream, keyStorePassword));
connection.addConnectionOptions(connOpts);
和最重要的 getSSLSocketFactory
方法是:
public SSLSocketFactory getSSLSocketFactory (InputStream keyStore, String password) throws MqttSecurityException {
try{
SSLContext ctx = null;
SSLSocketFactory sslSockFactory=null;
KeyStore ks;
ks = KeyStore.getInstance("PKCS12");
ks.load(keyStore, password.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(ks);
TrustManager[] tm = tmf.getTrustManagers();
ctx = SSLContext.getInstance("TLS");
ctx.init(null, tm, null);
sslSockFactory = ctx.getSocketFactory();
return sslSockFactory;
} catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException | KeyManagementException e) {
throw new MqttSecurityException(e);
}
}
这非常有效,但在 Android 低于 20 的 API 中没有成功。
终于找到了解决办法。
根据 this 文档,android API 级别 16(Android 4.1,Jelly Bean)支持 TLS 1.1 和 TLS 1.2。 但默认情况下不会启用,直到 API 20+ 级(Android 4.4 for watch,Kitkat Watch 和 Android 5.0 for phone, 棒棒糖).
所以我们只需要在代码中以语法方式启用它们。我们要怎么做?这个问题有一个解决方案 here 但它只是解决了您想要接受任何证书的情况。
我们需要的是做同样的事情,但使用我们自己的自签名证书。所以我们像下面那样做。第一部分就像我之前所做的一样:(keyStoreInputStream
是 .pfx 文件的输入流)
connection = createConnection(mqttCallback);
MqttConnectOptions connOpts = optionsFromModel(connectionModel);
connOpts.setSocketFactory(getSSLSocketFactory(keyStoreInputStream, keyStorePassword));
connection.addConnectionOptions(connOpts);
getSSLSocketFactory
方法更改为:
public SSLSocketFactory getSSLSocketFactory (InputStream keyStore, String password) throws MqttSecurityException {
try{
SSLContext ctx = null;
SSLSocketFactory sslSockFactory=null;
KeyStore ks;
ks = KeyStore.getInstance("PKCS12");
ks.load(keyStore, password.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(ks);
TrustManager[] tm = tmf.getTrustManagers();
ctx = SSLContext.getInstance("TLS");
ctx.init(null, tm, null);
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
sslSockFactory = new TLSSocketFactory(tm);
} else {
sslSockFactory = ctx.getSocketFactory();
}
return sslSockFactory;
} catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException | KeyManagementException e) {
throw new MqttSecurityException(e);
}
}
和 TLSSocketFactory
class 如下所示:
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
public class TLSSocketFactory extends SSLSocketFactory {
private SSLSocketFactory internalSSLSocketFactory;
public TLSSocketFactory(TrustManager[] trustManagers) throws KeyManagementException, NoSuchAlgorithmException {
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, trustManagers, null);
internalSSLSocketFactory = context.getSocketFactory();
}
@Override
public String[] getDefaultCipherSuites() {
return internalSSLSocketFactory.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return internalSSLSocketFactory.getSupportedCipherSuites();
}
@Override
public Socket createSocket() throws IOException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket());
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose));
}
@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort));
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort));
}
private Socket enableTLSOnSocket(Socket socket) {
if(socket != null && (socket instanceof SSLSocket)) {
((SSLSocket)socket).setEnabledProtocols(new String[] {"TLSv1.2", "TLSv1.1"});
}
return socket;
}
}
我在使用自签名证书连接到 mqtt 服务器时遇到问题。我正在使用 Paho 客户端并希望使用 TLSv1.2
连接到服务器。实际上,我在 Android API 20+ 中成功连接,但低于此版本没有成功。
到目前为止我做了什么:
1- 创建了一个 PKCS#12 密钥库并放入 .crt 文件并为其分配密码并保存(它将是一个 .pfx 文件)
2- 将 .pfx 文件添加到 android 项目
的资源原始文件夹中3- 使用以下代码加载自签名证书:
connection = createConnection(mqttCallback);
MqttConnectOptions connOpts = optionsFromModel(connectionModel);
connOpts.setSocketFactory(getSSLSocketFactory(keyStoreInputStream, keyStorePassword));
connection.addConnectionOptions(connOpts);
和最重要的 getSSLSocketFactory
方法是:
public SSLSocketFactory getSSLSocketFactory (InputStream keyStore, String password) throws MqttSecurityException {
try{
SSLContext ctx = null;
SSLSocketFactory sslSockFactory=null;
KeyStore ks;
ks = KeyStore.getInstance("PKCS12");
ks.load(keyStore, password.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(ks);
TrustManager[] tm = tmf.getTrustManagers();
ctx = SSLContext.getInstance("TLS");
ctx.init(null, tm, null);
sslSockFactory = ctx.getSocketFactory();
return sslSockFactory;
} catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException | KeyManagementException e) {
throw new MqttSecurityException(e);
}
}
这非常有效,但在 Android 低于 20 的 API 中没有成功。
终于找到了解决办法。
根据 this 文档,android API 级别 16(Android 4.1,Jelly Bean)支持 TLS 1.1 和 TLS 1.2。 但默认情况下不会启用,直到 API 20+ 级(Android 4.4 for watch,Kitkat Watch 和 Android 5.0 for phone, 棒棒糖).
所以我们只需要在代码中以语法方式启用它们。我们要怎么做?这个问题有一个解决方案 here 但它只是解决了您想要接受任何证书的情况。
我们需要的是做同样的事情,但使用我们自己的自签名证书。所以我们像下面那样做。第一部分就像我之前所做的一样:(keyStoreInputStream
是 .pfx 文件的输入流)
connection = createConnection(mqttCallback);
MqttConnectOptions connOpts = optionsFromModel(connectionModel);
connOpts.setSocketFactory(getSSLSocketFactory(keyStoreInputStream, keyStorePassword));
connection.addConnectionOptions(connOpts);
getSSLSocketFactory
方法更改为:
public SSLSocketFactory getSSLSocketFactory (InputStream keyStore, String password) throws MqttSecurityException {
try{
SSLContext ctx = null;
SSLSocketFactory sslSockFactory=null;
KeyStore ks;
ks = KeyStore.getInstance("PKCS12");
ks.load(keyStore, password.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(ks);
TrustManager[] tm = tmf.getTrustManagers();
ctx = SSLContext.getInstance("TLS");
ctx.init(null, tm, null);
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
sslSockFactory = new TLSSocketFactory(tm);
} else {
sslSockFactory = ctx.getSocketFactory();
}
return sslSockFactory;
} catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException | KeyManagementException e) {
throw new MqttSecurityException(e);
}
}
和 TLSSocketFactory
class 如下所示:
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
public class TLSSocketFactory extends SSLSocketFactory {
private SSLSocketFactory internalSSLSocketFactory;
public TLSSocketFactory(TrustManager[] trustManagers) throws KeyManagementException, NoSuchAlgorithmException {
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, trustManagers, null);
internalSSLSocketFactory = context.getSocketFactory();
}
@Override
public String[] getDefaultCipherSuites() {
return internalSSLSocketFactory.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return internalSSLSocketFactory.getSupportedCipherSuites();
}
@Override
public Socket createSocket() throws IOException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket());
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose));
}
@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort));
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort));
}
private Socket enableTLSOnSocket(Socket socket) {
if(socket != null && (socket instanceof SSLSocket)) {
((SSLSocket)socket).setEnabledProtocols(new String[] {"TLSv1.2", "TLSv1.1"});
}
return socket;
}
}