用于以编程方式将证书添加到 Java 密钥库的选项

我收到SSL握手异常错误:PKIX“路径不链接”(此处描述)。我通过使用openssl导入证书链来修复它:

openssl s_client -host www.envmgr.com -port 443 -showcerts > cert_chain.crt

并将其安装到我的JDK密钥库中:

keytool -import -alias envmgrchain -file cert_chain.crt -keystore cacerts -storepass changeit

嗯,这很有效。万岁。问题是,我们将把我们的应用程序放在像rackspace或AWS这样的云服务器上,我认为我们很有可能无法访问修改JVM的密钥库来添加这个链。

我想,“没问题,我只是以编程方式将此证书链添加到密钥库”,所以我从JVM中删除了它:

keytool -delete -alias envmgrchain -keystore cacerts -storepass changeit

并添加了以下代码:

    KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
    //Create an empty keystore that we can load certificate into
    trustStore.load(null);
    InputStream fis = new FileInputStream("cert_chain.crt");
    BufferedInputStream bis = new BufferedInputStream(fis);

    CertificateFactory cf = CertificateFactory.getInstance("X.509");
    while(bis.available()>0) {
        Collection<? extends Certificate> certs = cf.generateCertificates(bis);
        Iterator<? extends Certificate> iter = certs.iterator();
        //Add each cert in the chain one at a time
        for(int i=0; i<certs.size(); i++) {
            Certificate cert = iter.next();
            String alias = "chaincert"+((i>0)?i:"");
            trustStore.setCertificateEntry(alias, cert);
        }
    }
    bis.close();
    fis.close();
//Add custom keystore to TrustManager
    TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    tmf.init(trustStore);
    SSLContext ctx = SSLContext.getInstance("TLSv1");
    ctx.init(null, tmf.getTrustManagers(), null);

但是当我运行它时,PKIX错误再次出现。上面的代码不等同于keytool -import吗?我觉得我要么错误地将证书添加到密钥库,要么我没有以正确的方式将密钥库安装到TrustManager中。

仅供参考:我也试图通过实施X509TrustManager来解决这个问题。


答案 1

下面是可用于客户端在运行时以编程方式添加 CA 的代码。您无需将其放在任何商店中 - 只需随身携带PEM编码文件即可。您甚至可以将其硬编码到程序中,这样就没有单独的文件需要管理。

static String CA_FILE = "ca-cert.pem";
...

FileInputStream fis = new FileInputStream(CA_FILE);
X509Certificate ca = (X509Certificate) CertificateFactory.getInstance("X.509")
                        .generateCertificate(new BufferedInputStream(fis));

KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, null);
ks.setCertificateEntry(Integer.toString(1), ca);

TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);

SSLContext context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);
...

您将需要一个受信任的分发渠道,以确保您的程序在坐在服务器上等待被拾取或在安装时沿着线路行进时不会被修改。


openssl s_client -host www.envmgr.com -port 443 -showcerts > cert_chain.crt

您只需要信任根证书,而不需要信任整个链。服务器负责发送构建链所需的所有中间证书。如果服务器没有发送构建链所需的所有中间证书,则服务器配置错误。

您遇到的问题称为“哪个目录”问题。这是PKI中一个众所周知的问题。从本质上讲,这意味着客户端不知道去哪里获取丢失的中间证书。您可以通过让服务器发送所有必需的中间体以及服务器的证书来解决它。请参阅OWASP的TLS备忘单和规则 - 始终提供所有需要的证书


只是自行车脱落,但是Java这里有一大堆蠕虫(特别是Java 7及更低版本):

SSLContext ctx = SSLContext.getInstance("TLSv1");
ctx.init(null, tmf.getTrustManagers(), null);

如果需要,您可以对其进行改进。请参阅为 SSL 套接字启用哪些密码套件?。它缩小了Java中默认提供的协议版本,密码套件等中的一些差距。SSLSocketFactoryExSSLSocketFactory


答案 2

不要。您不应该修改 JRE 附带的文件。下次升级,您的更新将消失。您应该发布自己的信任库,该信任库由 JRE 附带的信任库以及要信任的任何额外证书构建而成。这是应用程序生成的一部分。

如果您随后想在运行时修改自己的信任库,请继续,但您需要注意,JVM 在重新启动之前不一定会看到更改:它肯定不会在您用于获取要添加的证书的相同范围内看到这些更改。SSLContext