spring 如何在Java中实现动态证书生成,类似于Golang的tls.config.GetCertificate

v8wbuo2f  于 2023-01-08  发布在  Spring
关注(0)|答案(1)|浏览(109)

比如围棋:

func main() {
ln, _ := net.Listen("tcp",":443")
ln = tls.NewListener(ln, &tls.Config{
    GetCertificate: func (info *clientHelloInfo) (Certificate, error) {
        // from db or dynamic generate a certificate use "info.ServerName"
        cert := generateX509Certificate(info.ServerName)
        return cert, nil
    }
})
}

在Go语言中,我可以实现在第一次访问一个域时(甚至是一个并发请求),只有一个goroutine负责生成证书,当证书生成完成后,所有的连接都立即成功。
不知道。因为Spring是通过Netty调用的,但是不知道如何修改Spring的Netty行为。

k4emjkb1

k4emjkb11#

结论

找不到像Golang这样可以直接生成SSL证书的软件包,相关代码样本均来自网络,修改后组合,仅供poc验证,相关许可和版权归原作者所有,代码经测试可执行。

示例代码

项目目录

├── pom.xml
└── src
    └── main
        └── java
            └── org
                └── example
                    └── Main.java

pom.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.example</groupId>
    <artifactId>TestSimpleHttpServer</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
<dependencies>
    <dependency>
        <groupId>org.bouncycastle</groupId>
        <artifactId>bcpkix-jdk18on</artifactId>
        <version>1.72</version>
    </dependency>
    <dependency>
        <groupId>org.bouncycastle</groupId>
        <artifactId>bcprov-jdk18on</artifactId>
        <version>1.72</version>
    </dependency>
</dependencies>
 <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>3.1.0</version>
        <executions>
          <execution>
            <goals>
              <goal>java</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <mainClass>org.example.Main</mainClass>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

Main.java

package org.example;

import com.sun.net.httpserver.*;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.TrustManagerFactory;
import javax.security.auth.x500.X500Principal;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.security.*;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.concurrent.Executors;

//MOST CODE COPY FROM
//https://docs.oracle.com/en/java/javase/17/docs/api/jdk.httpserver/com/sun/net/httpserver/package-summary.html

public class Main {
    public static X509Certificate generateCertificate(String dn, KeyPair keyPair, int days) throws Exception {
        Date from = new Date();
        Date to = new Date(from.getTime() + days * 86400000L);
        BigInteger sn = new BigInteger(64, new SecureRandom());
        X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
        X500Principal dnName = new X500Principal(dn);
        certGen.setSerialNumber(sn);
        certGen.setIssuerDN(dnName);
        certGen.setNotBefore(from);
        certGen.setNotAfter(to);
        certGen.setSubjectDN(dnName);
        certGen.setPublicKey(keyPair.getPublic());
        certGen.setSignatureAlgorithm("SHA1withRSA");
        return certGen.generate(keyPair.getPrivate());
    }
    public static void main(String[] args) throws Exception {
        System.out.println("App Start!");
        //Get Hostname
        InetAddress local = InetAddress.getLocalHost();
        String hostname=local.getHostName();
        System.out.println("hostname = "+hostname);
        System.out.println("Open https://localhost:8443/demoapp");
        //---- SSL ----

        //String dn="CN=localhost, OU=IT, O=DemoLab, L=Cupertino, ST=California, C=US";
        String dn="CN=" + hostname + ", OU=IT, O=DemoLab, L=Cupertino, ST=California, C=US";
        KeyPair keyPair =  KeyPairGenerator.getInstance("RSA").genKeyPair();

        X509Certificate  certificate = generateCertificate(dn,keyPair,365);
        //String certAlias ="localhost";
        String certAlias = hostname;

        char[] passphrase = "DummyPassword".toCharArray();
        KeyStore ks = KeyStore.getInstance("JKS");
        ks.load(null, passphrase);
        ks.setKeyEntry(certAlias, keyPair.getPrivate(), passphrase,  new X509Certificate[] { certificate });

        KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
        kmf.init(ks, passphrase);

        TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
        tmf.init(ks);

        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
//------HttpsServer
        HttpsServer server = HttpsServer.create(new InetSocketAddress(8443), 0);
        server.createContext("/demoapp", new MyHandler());
        server.setExecutor(Executors.newFixedThreadPool(10));
        server.setHttpsConfigurator (new HttpsConfigurator(sslContext) {
            public void configure (HttpsParameters params) {
                InetSocketAddress remote = params.getClientAddress();
                SSLContext c = getSSLContext();
                SSLParameters sslparams = c.getDefaultSSLParameters();
                //TODO
                /*
                if (remote.equals (...) ) {
                    // modify the default set for client x
                }
                 */
                params.setSSLParameters(sslparams);
                // statement above could throw IAE if any params invalid.
                // eg. if app has a UI and parameters supplied by a user.
            }
        });
        server.start();
    }

    public static class MyHandler implements HttpHandler {
        public void handle(HttpExchange httpExchange) throws IOException {
        //Hard code test
        InputStream is = httpExchange.getRequestBody();
        //TODO
        //read(is); // .. read the request body
        String response = "Hello World!";
        httpExchange.sendResponseHeaders(200, response.length());
        OutputStream os = httpExchange.getResponseBody();
        os.write(response.getBytes());
        os.close();

        }
    }
}

要求

  • JDK 17语言
  • 梅文

运行应用程序

在项目目录下执行命令:
第一个月

打开

浏览器打开https://localhost:8443/demoapp

相关问题