openssl 生成多域名 多IP 的数字证书
openssl.cnf 文件内容:
[req] default_bits = 2048 distinguished_name = req_distinguished_name copy_extensions = copy req_extensions = req_ext x509_extensions = v3_req prompt = no [req_distinguished_name] countryName = CN stateOrProvinceName = GuangDong localityName = ShenZhen organizationName = lc commonName = CA [req_ext] basicConstraints = CA:FALSE subjectAltName = @alt_names [v3_req] basicConstraints = CA:FALSE subjectAltName = @alt_names [alt_names] IP.1 = 192.168.10.31 IP.2 = 192.168.10.32 IP.3 = 192.168.10.33 DNS.1 = 192.168.10.2 DNS.2 = 202.96.134.133
生成证书
工具是用的:windows平台 Win64OpenSSL-3_2_0.exe 或 Win64OpenSSL_Light-3_2_0.exe (建议用:Win64OpenSSL-3_2_0.exe )
OpenSSL 3.2.0 23 Nov 2023 (Library: OpenSSL 3.2.0 23 Nov 2023)
根证书: openssl genrsa -out ca.key 2048 openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.pem -subj "/C=CN/ST=GuangDong/O=EMQX/CN=Client" 服务端证书: openssl genrsa -out emqx.key 2048 openssl req -new -key emqx.key -config openssl.cnf -out emqx.csr openssl x509 -req -in emqx.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out emqx.pem -days 3650 -sha256 -extensions v3_req -extfile openssl.cnf 客户端证书: openssl genrsa -out client.key 2048 openssl req -new -key client.key -out client.csr -subj "/C=CN/ST=GuangDong/O=EMQX/CN=Client" openssl x509 -req -days 3650 -in client.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out client.pem 校验证书的有效性: openssl verify -CAfile ca.pem emqx.pem openssl verify -CAfile ca.pem client.pem
常见错误:
Error [ERR_TLS_CERT_ALTNAME_INVALID]: Hostname/IP does not match certificate's altnames: IP: 192.168.10.32 is not in the cert's list:
Error: self signed certificate in certificate chain
Error: Connection refused: Not authorized # 没有设置用户名密码
Error: unable to verify the first certificate
加密认证算法:
package com.lc.common.mqtt.utils; import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; import org.springframework.core.io.ResourceLoader; import org.springframework.stereotype.Component; import java.io.*; import java.security.KeyStore; import java.security.PrivateKey; import java.security.Security; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManagerFactory; /** * @author Charley * @date 2022/12/05 * @description */ @Component public class SSLUtils { @javax.annotation.Resource private ResourceLoader resourceLoader; public SSLSocketFactory getSingleSocketFactory(InputStream caCrtFileInputStream) throws Exception { Security.addProvider(new BouncyCastleProvider()); X509Certificate caCert = null; BufferedInputStream bis = new BufferedInputStream(caCrtFileInputStream); CertificateFactory cf = CertificateFactory.getInstance("X.509"); while (bis.available() > 0) { caCert = (X509Certificate) cf.generateCertificate(bis); } KeyStore caKs = KeyStore.getInstance(KeyStore.getDefaultType()); caKs.load(null, null); caKs.setCertificateEntry("cert-certificate", caCert); TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(caKs); SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); sslContext.init(null, tmf.getTrustManagers(), null); return sslContext.getSocketFactory(); } public SSLSocketFactory getSocketFactory(final String caCrtFile, final String crtFile, final String keyFile, final String password) throws Exception { Security.addProvider(new BouncyCastleProvider()); // load CA certificate X509Certificate caCert = null; // FileInputStream fis = new FileInputStream(caCrtFile); BufferedInputStream bis = new BufferedInputStream(resourceLoader.getResource(caCrtFile).getInputStream()); CertificateFactory cf = CertificateFactory.getInstance("X.509"); while (bis.available() > 0) { caCert = (X509Certificate) cf.generateCertificate(bis); } // load client certificate //bis = new BufferedInputStream(new FileInputStream(crtFile)); bis = new BufferedInputStream(resourceLoader.getResource(crtFile).getInputStream()); X509Certificate cert = null; while (bis.available() > 0) { cert = (X509Certificate) cf.generateCertificate(bis); } // load client private key // PEMParser pemParser = new PEMParser(new FileReader(keyFile)); // Object object = pemParser.readObject(); // JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC"); // KeyPair key = converter.getKeyPair((PEMKeyPair) object); // pemParser.close(); // PEMParser pemParser =new PEMParser(new InputStreamReader(new FileInputStream(keyFile))); PEMParser pemParser =new PEMParser(new InputStreamReader(resourceLoader.getResource(keyFile).getInputStream())); Object obj = pemParser.readObject(); JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); PrivateKey privateKey = converter.getPrivateKey((PrivateKeyInfo) obj); // 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("X509"); tmf.init(caKs); // client key and certificates are sent to server, so it can authenticate KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); ks.load(null, null); ks.setCertificateEntry("certificate", cert); ks.setKeyEntry("private-key", privateKey, password.toCharArray(), new java.security.cert.Certificate[]{cert}); KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory .getDefaultAlgorithm()); kmf.init(ks, password.toCharArray()); // finally, create SSL socket factory SSLContext context = SSLContext.getInstance("TLSv1.2"); context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); return context.getSocketFactory(); } }
mqq5:
package com.lc.common.mqtt.mqttv5; import cn.hutool.core.util.IdUtil; import com.lc.common.mqtt.config.MqttConfig; import com.lc.common.mqtt.utils.SSLUtils; import lombok.extern.slf4j.Slf4j; import org.eclipse.paho.mqttv5.client.MqttConnectionOptions; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.integration.annotation.ServiceActivator; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.core.MessageProducer; import org.springframework.integration.mqtt.outbound.Mqttv5PahoMessageHandler; import org.springframework.integration.mqtt.support.MqttHeaderMapper; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHandler; import javax.annotation.Resource; @Configuration @Slf4j public class Mqtt5Client { @Resource MqttConfig mc; @Resource private SSLUtils sslUtils; @Resource private Mqtt5MessageReceiver mqttMessageReceiver; /** * (生产者) mqtt消息出站通道,用于发送出站消息 * @return */ @Bean public MessageChannel mqttOutputChannel5() { return new DirectChannel(); } /** * (消费者) mqtt消息入站通道,订阅消息后消息进入的通道。 * @return */ @Bean public MessageChannel mqttInputChannel5() { return new DirectChannel(); } public MqttConnectionOptions getOptions() { MqttConnectionOptions options = new MqttConnectionOptions(); options.setServerURIs(mc.getServices()); options.setUserName(mc.getUser()); options.setPassword(mc.getPassword().getBytes()); options.setReceiveMaximum(mc.getMaxInflight()); options.setKeepAliveInterval(mc.getKeepAliveInterval()); // 重连设置 options.setAutomaticReconnect(mc.isAutomaticReconnect()); options.setMaxReconnectDelay(mc.getMaxReconnectDelay()); options.setAutomaticReconnectDelay(mc.getV5AutomaticReconnectMinDelay(), mc.getV5AutomaticReconnectMaxDelay()); // 会话设置 options.setCleanStart(mc.isV5CleanStart()); options.setSessionExpiryInterval(mc.getV5SessionExpiryInterval()); // 超时设置 options.setConnectionTimeout(mc.getConnectionTimeout()); try { options.setSocketFactory(sslUtils.getSocketFactory( "classpath:ca.pem", "classpath:client.pem", "classpath:client.key", "")); } catch (Exception e) { e.printStackTrace(); } return options; } /** * 生产者 * @return */ @Bean @ServiceActivator(inputChannel = "mqttOutputChannel5") public MessageHandler mqttOutbound5() { String clientId = mc.getV5ProducerId() + "_" + IdUtil.getSnowflakeNextId();; Mqttv5PahoMessageHandler messageHandler = new Mqttv5PahoMessageHandler(getOptions(), clientId); messageHandler.setHeaderMapper(new MqttHeaderMapper()); // 设置异步不阻塞 messageHandler.setAsync(false); // 设置Qos messageHandler.setDefaultQos(mc.getQos()); return messageHandler; } /** * MQTT消息订阅绑定(消费者) * @return */ @Bean public MessageProducer channelInbound5(MessageChannel mqttInputChannel5) { String clientId = mc.getV5ConsumerId() + "_" + IdUtil.getSnowflakeNextId();; MyMqttv5PahoMessageDrivenChannelAdapter adapter = new MyMqttv5PahoMessageDrivenChannelAdapter(getOptions(), clientId, mc.getV5DefaultTopic()); adapter.setCompletionTimeout(mc.getCompletionTimeout()); adapter.setPayloadType(String.class); adapter.setQos(mc.getQos()); adapter.setOutputChannel(mqttInputChannel5); return adapter; } /** * MQTT消息处理器(消费者) * @return */ @Bean @ServiceActivator(inputChannel = "mqttInputChannel5") public MessageHandler mqttMessageHandler5() { return mqttMessageReceiver; } }
mqtt3
package com.lc.common.mqtt.mqttv3; import cn.hutool.core.util.IdUtil; import com.lc.common.mqtt.utils.SSLUtils; import lombok.extern.slf4j.Slf4j; import org.eclipse.paho.client.mqttv3.MqttConnectOptions; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.integration.annotation.ServiceActivator; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.core.MessageProducer; import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory; import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter; import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler; import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHandler; import com.lc.common.mqtt.config.MqttConfig; import javax.annotation.Resource; @Configuration @Slf4j public class Mqtt3Client { @Resource private MqttConfig mc; @Resource private SSLUtils sslUtils; @Resource private Mqtt3MessageReceiver mqttMessageReceiver; /** * (生产者) mqtt消息出站通道,用于发送出站消息 * @return */ @Bean public MessageChannel mqttOutputChannel3() { return new DirectChannel(); } /** * (消费者) mqtt消息入站通道,订阅消息后消息进入的通道。 * @return */ @Bean public MessageChannel mqttInputChannel3() { return new DirectChannel(); } public MqttConnectOptions getOptions() { MqttConnectOptions options = new MqttConnectOptions(); options.setServerURIs(mc.getServices()); options.setUserName(mc.getUser()); options.setPassword(mc.getPassword().toCharArray()); options.setMaxInflight(mc.getMaxInflight()); options.setKeepAliveInterval(mc.getKeepAliveInterval()); // 重连设置 options.setAutomaticReconnect(mc.isAutomaticReconnect()); options.setMaxReconnectDelay(mc.getMaxReconnectDelay()); // options.setAutomaticReconnectDelay(automaticReconnectMinDelay, automaticReconnectMaxDelay); // 会话设置 options.setCleanSession(mc.isV3CleanSession()); // 超时设置 options.setConnectionTimeout(mc.getConnectionTimeout()); // 设置遗嘱消息 qos 默认为 1 retained 默认为 false options.setWill("willTopic","与服务器断开连接".getBytes(),0,false); try { options.setSocketFactory(sslUtils.getSocketFactory( "classpath:ca.pem", "classpath:client.pem", "classpath:client.key", "")); } catch (Exception e) { e.printStackTrace(); } return options; } /** * 生产者 * @return */ @Bean @ServiceActivator(inputChannel = "mqttOutputChannel3") public MessageHandler mqttOutbound3() { String clientId = mc.getV3ProducerId() + "_" + IdUtil.getSnowflakeNextId(); DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory() ; factory.setConnectionOptions(getOptions()); MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler(clientId, factory); // 设置异步不阻塞 messageHandler.setAsync(true); // 设置Qos messageHandler.setDefaultQos(mc.getQos()); return messageHandler; } /** * MQTT消息订阅绑定(消费者) * @return */ @Bean public MessageProducer channelInbound3(MessageChannel mqttInputChannel3) { String clientId = mc.getV3ConsumerId() + "_" + IdUtil.getSnowflakeNextId();; DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory(); factory.setConnectionOptions(getOptions()); MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter(clientId, factory, mc.getV3DefaultTopic()); adapter.setCompletionTimeout(mc.getCompletionTimeout()); adapter.setRecoveryInterval(mc.getV3RecoveryInterval()); adapter.setConverter(new DefaultPahoMessageConverter()); adapter.setQos(mc.getQos()); adapter.setOutputChannel(mqttInputChannel3); return adapter; } /** * MQTT消息处理器(消费者) * @return */ @Bean @ServiceActivator(inputChannel = "mqttInputChannel3") public MessageHandler mqttMessageHandler3() { return mqttMessageReceiver; } }