Java KeyStore中以编程方式添加证书的选项

20

我遇到了SSL握手异常错误:PKIX “path does not chain”(在此处描述)。 通过使用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吗?我觉得我可能要么是错误地将证书添加到Keystore中,要么没有以正确的方式将Keystore安装到TrustManager中。

FYI:我还试图通过实现X509TrustManager来解决此问题。


你可以将你想要使用的 truststore 与应用程序一起打包,并通过 vm 参数引用它,这样怎么样? - hooknc
这里有一个由Andreas Sterbenz创建的著名示例,名为InstallCert,它可以完成您正在尝试做的事情,但是您需要分析代码才能确切得到所需内容:http://code.google.com/p/java-use-examples/source/browse/trunk/src/com/aw/ad/util/InstallCert.java - hooknc
2个回答

31

以下是您可以提供给客户端以在运行时程序化添加您的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

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

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


只是一些琐碎的讨论,但在Java中(特别是Java 7及以下版本)存在另一个问题:

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

如有需要,您可以改进它。请参见Which Cipher Suites to enable for SSL Socket?上的SSLSocketFactoryEx。它会关闭Java SSLSocketFactory默认提供的某些协议版本、密码套件等方面的一些漏洞。


2
这里有很多好的信息-谢谢!我目前所做的是自己提供证书。我创建了一个自定义的X509TrustManager,当从默认管理器捕获CertificateException时,它会循环遍历我存储在名为“trustedCerts”的目录中的一堆证书。我将它们转换为X509证书,并使用X509Certificate.equals()对链进行比较检查...如果匹配,则接受该证书。你认为在这里使用equals是有效的比较吗?(顺便说一下,如果这个问题对于评论来说太大了,请告诉我,我会为它开始一个单独的问题)。 - IcedDante
1
我目前所做的是自己提供证书...使用自定义的X509TrustManager... - 是的,携带服务器预期的证书是可以的。这被称为固定,它是一个很好的安全增强功能。它比Web安全模型更好。请参阅OWASP的证书和公钥固定 - jww
1
只是一个提示。当在一行中将CA硬编码为字符串时,请在换行符的位置添加\n。虽然我不建议这种方法。但我需要它进行测试。 - v0rin

0
不要这样做。您不应该修改随JRE提供的文件。下一次升级,您的更新将会消失。您应该使用自己的信任存储库,从随JRE提供的那个信任存储库中构建,并添加任何额外的证书以进行信任。这是应用程序构建的一部分。
如果您想在运行时修改自己的信任存储库,请继续,但是您需要知道JVM在重启之前不一定会看到更改:它肯定不会在您用来获取想要添加的证书的同一 SSLContext 内看到它们。

如果您想在运行时修改自己的信任存储库,请继续操作,但是需要注意的是,JVM 在重新启动之前可能不会看到更改:它肯定不会在您用于获取要添加的证书的相同 SSLContext 中看到它们。 - IcedDante
这是一个逻辑推论,因为KeyStore是从输入流中加载而不是提供文件。 - user207421

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接