在Java中,仅使用PEM文件创建SSLContext的最简单方法是什么?

2022-09-01 23:51:37

我使用LetsEncrypt的CertBot免费生成PEM文件。在其他语言中,只需几行代码和PEM /密钥文件即可轻松启动HTTPS服务器。到目前为止,我在java中发现的解决方案过于复杂,我正在寻找更简单的东西。

  1. 我不想使用java的命令行“keytool”。我只想将我的PEM /密钥文件拖放到我的日食中,并使用SSLContext以编程方式启动HTTPS服务器。
  2. 我不想包括像BouncyCastle这样的大型外部库。请参阅以下链接,了解使用BouncyCastle的假定解决方案:如何从PEM证书和密钥构建SSSocketFactory而不转换为密钥库?

有没有更好/更简单的方法来做到这一点?


答案 1

以下代码通常演示如何通过解析具有多个条目(例如,多个证书和一个 )的 PEM 文件来为 HTTPS 服务器创建 SSLContext。但是它是不完整的,因为普通的Java 8无法解析PKCS#1 RSA私钥数据。因此,您似乎无法在没有任何库的情况下执行此操作。至少需要用于解析PKCS#1数据的BouncyCastle(然后也可以使用BouncyCastle的PEM解析器)。RSA PRIVATE KEY

private SSLContext createSslContext() throws Exception {
    URL url = getClass().getResource("/a.pem");
    InputStream in = url.openStream();
    String pem = new String(in.readAllBytes(), StandardCharsets.UTF_8);
    Pattern parse = Pattern.compile("(?m)(?s)^---*BEGIN ([^-]+)---*$([^-]+)^---*END[^-]+-+$");
    Matcher m = parse.matcher(pem);
    CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
    Decoder decoder = Base64.getMimeDecoder();
    List<Certificate> certList = new ArrayList<>(); // java.security.cert.Certificate

    PrivateKey privateKey = null;

    int start = 0;
    while (m.find(start)) {
        String type = m.group(1);
        String base64Data = m.group(2);
        byte[] data = decoder.decode(base64Data);
        start += m.group(0).length();
        type = type.toUpperCase();
        if (type.contains("CERTIFICATE")) {
            Certificate cert = certFactory.generateCertificate(new ByteArrayInputStream(data));
            certList.add(cert);
        } else if (type.contains("RSA PRIVATE KEY")) {
            // TODO: load and parse PKCS1 data structure to get the RSA private key  
            privateKey = ...
        } else {
            System.err.println("Unsupported type: " + type);
        }

    }
    if (privateKey == null)
        throw new RuntimeException("RSA private key not found in PEM file");

    char[] keyStorePassword = new char[0];

    KeyStore keyStore = KeyStore.getInstance("JKS");
    keyStore.load(null, null);

    int count = 0;
    for (Certificate cert : certList) {
        keyStore.setCertificateEntry("cert" + count, cert);
        count++;
    }
    Certificate[] chain = certList.toArray(new Certificate[certList.size()]);
    keyStore.setKeyEntry("key", privateKey, keyStorePassword, chain);

    TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    tmf.init(keyStore);
    KeyManagerFactory kmf = KeyManagerFactory.getInstance("RSA");
    kmf.init(keyStore, keyStorePassword);
    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
    return sslContext;
}

答案 2

虽然已经提供了答案,但我想提供一个需要更少代码的替代方法。有关设置示例,请参阅下文:

X509ExtendedKeyManager keyManager = PemUtils.loadIdentityMaterial("certificate-chain.pem", "private-key.pem", "private-key-password".toCharArray());
X509ExtendedTrustManager trustManager = PemUtils.loadTrustMaterial("some-trusted-certificate.pem");

SSLFactory sslFactory = SSLFactory.builder()
          .withIdentityMaterial(keyManager)
          .withTrustMaterial(trustManager)
          .build();

SSLContext sslContext = sslFactory.getSslContext();

要使用上述设置,您可以使用此库:

<dependency>
    <groupId>io.github.hakky54</groupId>
    <artifactId>sslcontext-kickstart-for-pem</artifactId>
    <version>7.0.0</version>
</dependency>

上述设置需要较少的自定义代码,同时它仍在实现您尝试完成的目标。您可以在此处查看库和文档:https://github.com/Hakky54/sslcontext-kickstart