#!/bin/bash # author:xijin.c read -r -p "请输入文件存储目录名: " dir cd /data mkdir $dir cd $dir # 生成CA key【采用2048字节】 openssl genrsa -out ca.key 2048 # 生成CA 证书【默认3650天】 openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -subj "/CN=www.emqx.io" -out ca.pem read -r -p "请输入服务端ip: " serverIp openssl genrsa -out server.key 2048 # 注意将IP修改为服务器IP openssl req -new -key ./server.key -out server.csr -subj "/CN=$serverIp" openssl x509 -req -in ./server.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out server.pem -days 3650 -sha256 read -r -p "请输入客户端ip: " clientIp openssl genrsa -out client.key 2048 #注意将IP修改为客户端IP openssl req -new -key ./client.key -out client.csr -subj "/CN=$clientIp" openssl x509 -req -in ./client.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out client.pem -days 3650 -sha256 #将ca.pem 与 client.pem 转化为.crt格式 openssl x509 -outform der -in ca.pem -out ca.crt openssl x509 -outform der -in client.pem -out client.crt #将client.key转换为.pem文件【java代码连接需要】 openssl pkcs8 -topk8 -inform PEM -in client.key -outform PEM -nocrypt -out client-key-pkcs8.pem
1.生成CA key和证书
cd /etc/pki/CA 生成CA key【采用2048字节】 openssl genrsa -out ca.key 2048 生成CA 证书【默认3650天】 openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -subj "/CN=www.emqx.io" -out ca.pem
2.生成服务端key和证书
openssl genrsa -out server.key 2048 注意将IP修改为服务器IP openssl req -new -key ./server.key -out server.csr -subj "/CN=服务器IP" openssl x509 -req -in ./server.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out server.pem -days 3650 -sha256
3.生成客户端key和证书
openssl genrsa -out client.key 2048 注意将IP修改为客户端IP openssl req -new -key ./client.key -out client.csr -subj "/CN=服务器IP" openssl x509 -req -in ./client.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out client.pem -days 3650 -sha256
4.修改emqx配置文件emqx.conf 我这里emqx docker 镜像版本为: emqx/emqx 4.2.2
将生成的CA文件复制到/opt/emqx/etc/certs目录下:docker cp server.pem emqx:/opt/emqx/etc/certs/.
vi /opt/emqx/etc/emqx.conf ## SSL Options listener.ssl.external.keyfile = /opt/emqx/etc/certs/server.key listener.ssl.external.certfile = /opt/emqx/etc/certs/server.pem ## 开启双向认证 listener.ssl.external.cacertfile = /opt/emqx/etc/certs/ca.pem listener.ssl.external.verify = verify_peer listener.ssl.external.fail_if_no_peer_cert = true
启用用户名密码验证【可选】
关闭匿名认证,使用用户名密码认证
#先要把emq的匿名认证关了,在emqx.conf文件 allow_anonymous = false **重启emqx服务** #加载用户名认证插件 在docker 容器内部执行 cd /opt/emqx ./bin/emqx_ctl plugins load emqx_auth_username #添加用户 ./bin/emqx_ctl users add <Username> <Password>
5.使用MQTT.fx客户端验证是否配置成功
mqtt.fx下载地址:http://www.jensd.de/apps/mqttfx/
这里选用的是windows1.7.1的版本
MQTT.fx客户端使用
填写用户名密码信息
**配置客户端ssl连接证书
java使用证书准备
cd 进入证书所在目录: /etc/pki/CA/ 将ca.pem 与 client.pem 转化为.crt格式 openssl x509 -outform der -in your-ca.pem -out your-ca.crt openssl x509 -outform der -in your-client.pem -out your-client.crt 将client.key转换为.pem文件【java代码连接需要】 openssl pkcs8 -topk8 -inform PEM -in client.key -outform PEM -nocrypt -out client-key-pkcs8.pem
Java代码
<dependency> <groupId>org.eclipse.paho</groupId> <artifactId>org.eclipse.paho.client.mqttv3</artifactId> <version>1.2.0</version> </dependency>
MQTT静态常量类
/** * MQTT静态参数常量类 */ public class MqttConstant {
/** * CA证书 */ public final static String SSL_CA_CRT = "E:/工具/mqtt/方式2/ca.crt"; /** * 客户端证书 client.crt证书文件路径 */ public final static String SSL_CLIENT_CRT = "E:/工具/mqtt/方式2/client.crt"; /** * 客户端证书key client-key-pkcs8.pem证书文件路径 */ public final static String SSL_CLIENT_KEY_PKCS8_PEM = "E:/工具/mqtt/方式2/client-key-pkcs8.pem"; /** * 客户端证书密码 */ public final static String SSL_CLIENT_PASSWORD = "";
}
import org.apache.commons.codec.binary.Base64; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.InputStreamReader; import java.security.KeyFactory; import java.security.KeyStore; import java.security.PrivateKey; import java.security.SecureRandom; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.interfaces.RSAPrivateKey; import java.security.spec.PKCS8EncodedKeySpec; public class EmqxSSLFactory { public static javax.net.ssl.SSLSocketFactory getSSLSocktet(String caPath, String crtPath, String keyPath, String password) { try{ CertificateFactory cAf = CertificateFactory.getInstance("X.509"); FileInputStream caIn = new FileInputStream(caPath); X509Certificate ca = (X509Certificate) cAf.generateCertificate(caIn); KeyStore caKs = KeyStore.getInstance("JKS"); caKs.load(null, null); caKs.setCertificateEntry("ca-certificate", ca); TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX"); tmf.init(caKs); CertificateFactory cf = CertificateFactory.getInstance("X.509"); FileInputStream crtIn = new FileInputStream(crtPath); X509Certificate caCert = (X509Certificate) cf.generateCertificate(crtIn); crtIn.close(); KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); ks.load(null, null); ks.setCertificateEntry("certificate", caCert); ks.setKeyEntry("private-key", getPrivateKey(keyPath), password.toCharArray(), new java.security.cert.Certificate[]{caCert}); KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX"); kmf.init(ks, password.toCharArray()); SSLContext context = SSLContext.getInstance("TLSv1.2"); context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom()); return context.getSocketFactory(); }catch (Exception e){ e.printStackTrace(); } return null; } public static PrivateKey getPrivateKey(String path) throws Exception { org.apache.commons.codec.binary.Base64 base64 = new Base64(); byte[] buffer = base64.decode(getPem(path)); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); return (RSAPrivateKey) keyFactory.generatePrivate(keySpec); } private static String getPem(String path) throws Exception { FileInputStream fin = new FileInputStream(path); BufferedReader br = new BufferedReader(new InputStreamReader(fin)); String readLine = null; StringBuilder sb = new StringBuilder(); while ((readLine = br.readLine()) != null) { if (readLine.charAt(0) == '-') { continue; } else { sb.append(readLine); sb.append('\r'); } } fin.close(); return sb.toString(); } }
或者
--需要引入低版本bouncycastle包 我这里jar冲突没有使用 <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcpkix-jdk15on</artifactId> <version>1.47</version> </dependency>
import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.PEMReader; import org.bouncycastle.openssl.PasswordFinder; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManagerFactory; import java.io.ByteArrayInputStream; import java.io.InputStreamReader; import java.nio.file.Files; import java.nio.file.Paths; import java.security.KeyPair; import java.security.KeyStore; import java.security.Security; import java.security.cert.X509Certificate; public class SslUtil { public static SSLSocketFactory getSocketFactory(final String caCrtFile, final String crtFile, final String keyFile, final String password) throws Exception { Security.addProvider(new BouncyCastleProvider()); // load CA certificate PEMReader reader = new PEMReader(new InputStreamReader(new ByteArrayInputStream(Files.readAllBytes(Paths.get(caCrtFile))))); X509Certificate caCert = (X509Certificate)reader.readObject(); reader.close(); // load client certificate reader = new PEMReader(new InputStreamReader(new ByteArrayInputStream(Files.readAllBytes(Paths.get(crtFile))))); X509Certificate cert = (X509Certificate)reader.readObject(); reader.close(); // load client private key reader = new PEMReader( new InputStreamReader(new ByteArrayInputStream(Files.readAllBytes(Paths.get(keyFile)))), new PasswordFinder() { @Override public char[] getPassword() { return password.toCharArray(); } } ); KeyPair key = (KeyPair)reader.readObject(); reader.close(); // CA certificate is used to authenticate server KeyStore caKs = KeyStore.getInstance(KeyStore.getDefaultType()); caKs.load(null, null); caKs.setCertificateEntry("ca-certificate", caCert); TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(caKs); // client key and certificates are sent to server so it can authenticate us KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); ks.load(null, null); ks.setCertificateEntry("certificate", cert); ks.setKeyEntry("private-key", key.getPrivate(), password.toCharArray(), new java.security.cert.Certificate[]{cert}); KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); kmf.init(ks, password.toCharArray()); //todo:这里会出现问题 我用的是jdk1.8 对应TLSv1.2 --- javax.net.ssl.SSLException: Received fatal alert: protocol_version // finally, create SSL socket factory SSLContext context = SSLContext.getInstance("TLSv1.2"); context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); return context.getSocketFactory(); } }
连接验证
public void connect(MqttConfiguration inCon) throws Exception { try { configuration = inCon; client = new MqttAsyncClient(configuration.getBrokerIp(), configuration.getClientId(), new MemoryPersistence()); client.setCallback(this); clientOptions = new MqttConnectOptions(); // if (configuration.isSsl() && !StringUtils.isEmpty(configuration.getTruststore())) { // Properties sslProperties = new Properties(); // sslProperties.put(SSLSocketFactoryFactory.TRUSTSTORE, configuration.getTruststore()); // sslProperties.put(SSLSocketFactoryFactory.TRUSTSTOREPWD, configuration.getTruststorePassword()); // sslProperties.put(SSLSocketFactoryFactory.TRUSTSTORETYPE, "JKS"); // sslProperties.put(SSLSocketFactoryFactory.CLIENTAUTH, false); // clientOptions.setSSLProperties(sslProperties); // } MqttClientCredentialsFactory clientCredentialsFactory = new MqttClientCredentialsFactory(); clientCredentialsFactory.getMqttClientCredentials(configuration.getAuth()).configure(clientOptions, configuration); clientOptions.setCleanSession(true); SSLSocketFactory factory = EmqxSSLFactory.getSSLSocktet(MqttConstant.SSL_CA_CRT, MqttConstant.SSL_CLIENT_CRT, MqttConstant.SSL_CLIENT_KEY_PKCS8_PEM, MqttConstant.SSL_CLIENT_PASSWORD); clientOptions.setSocketFactory(factory); checkConnection(); } catch (MqttException e) { log.error("{} MQTT broker connection failed!", configuration.getBrokerIp(), e); throw new RuntimeException("MQTT broker connection failed!", e); } }
添加ssl认证:【EMQ记录】指定的 SocketFactory 类型与代理程序 URI 不匹配问题
是因为MQTT-Client 使用了ssl连接,但是mqtt的URL,要使用SSL,不要使用TCP;
例如
tcp://192.168.111:1883 替换为 ssl://192.168.111:8883