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;
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章