Java SSL - InstallCert识别证书,但仍出现“无法找到有效的认证路径”错误?

7

我认为我遇到了和其他人一样的问题,因此我已经查看了许多类似的问题和潜在解决方案,但是都没有用。

我使用的信任存储是cacerts,位于Java 1.6.0 JRE(build 1.6.0_20-b02)的lib / security中(这可能是问题的根源?)。 我也尝试过使用jssecacerts。

根据其他类似问题的发布,我使用InstallCert后,可以看到我的证书已安装并有效(我已删除它,重新导入它等,以确保我看到正确的数据):

java InstallCert <my host name>
Loading KeyStore jssecacerts...
Opening connection to <my host name>:443...
Starting SSL handshake...
No errors, certificate is already trusted

在 keytool 和 Portecle 中检查并重新导入证书(我尝试过使用 -showcert 从 openssl 生成、从浏览器导出并通过 scp 传输等方法),会出现“此内容已经存在于其他别名下”的消息。因此,证书进入工具的方式似乎没有任何问题。

在代码中强制指定 trustStore 路径没有任何区别,在所有情况下,当我通过将 javax.net.debug 属性设置为“all”来打开调试时,我看到的结果是:

main, SEND TLSv1 ALERT:  fatal, description = certificate_unknown
main, WRITE: TLSv1 Alert, length = 2 [Raw write]: length = 7 0000: 15
03 01 00 02 02 2E                               ....... main, called
closeSocket() main, handling exception:
javax.net.ssl.SSLHandshakeException:
sun.security.validator.ValidatorException: PKIX path building failed:
sun.security.provider.certpath.SunCertPathBuilderException: unable to
find valid certification path to requested target

很不幸,我不能通过实现自己的TrustManager来覆盖检查 - 它必须进行实际检查。

我从主机获取的证书有许多扩展(确切地说是9个),这让我想知道它们是否与此问题有关。

我还可以检查/尝试什么?切换到不同的JRE版本?


为什么你不能自己实现TrustManager?你仍然可以使用你的信任管理器中的证书并进行检查。 - Vivin Paliath
@Vivin - 我试图解决的核心问题在于一个应用层,我无法直接控制(在这种情况下是一个调用一些最近变成 HTTPS-only 的 Web 服务的 Grails 应用程序),因此虽然我可以在自己的本地场景中使用它,但我不能强制其在上游使用。这很痛苦,但我稍后会处理它 - 现在,如果我能在最精细的级别使其工作,至少告诉我下一步该去哪里解决问题。 - Bill
你是否在像GlassFish这样的应用服务器下运行此程序?你可以检查一下进程是否在 /usr/lib/jvm/java-1.6.0-sun-1.6.0.20.x86_64 中运行Java二进制文件,而不是其他不支持你的trustStore(如OpenJDK等)的Java二进制文件。你发布的调试输出很有帮助,你能再发布更多吗? - Brad
3个回答

0
你仍然可以通过实现自己的信任管理器来检查证书。我遇到了类似的问题这里。我也尝试将证书添加到cacerts,但没有用。
在你的信任管理器中,你需要显式地加载证书。基本上,我所做的就像这样:
首先,我创建了一个使用实际证书文件的信任管理器:
public class ValicertX509TrustManager implements X509TrustManager {

    X509TrustManager pkixTrustManager;

    ValicertX509TrustManager() throws Exception {

        String valicertFile = "/certificates/ValicertRSAPublicRootCAv1.cer";
        String commwebDRFile = "/certificates/DR_10570.migs.mastercard.com.au.crt";
        String commwebPRODFile = "/certificates/PROD_10549.migs.mastercard.com.au.new.crt";

        Certificate valicert = CertificateFactory.getInstance("X509").generateCertificate(this.getClass().getResourceAsStream(valicertFile));
        Certificate commwebDR = CertificateFactory.getInstance("X509").generateCertificate(this.getClass().getResourceAsStream(commwebDRFile));
        Certificate commwebPROD = CertificateFactory.getInstance("X509").generateCertificate(this.getClass().getResourceAsStream(commwebPRODFile));

        KeyStore keyStore = KeyStore.getInstance("JKS");
        keyStore.load(null, "".toCharArray());
        keyStore.setCertificateEntry("valicert", valicert);
        keyStore.setCertificateEntry("commwebDR", commwebDR);
        keyStore.setCertificateEntry("commwebPROD", commwebPROD);

        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("PKIX");
        trustManagerFactory.init(keyStore);

        TrustManager trustManagers[] = trustManagerFactory.getTrustManagers();

        for(TrustManager trustManager : trustManagers) {
            if(trustManager instanceof X509TrustManager) {
                pkixTrustManager = (X509TrustManager) trustManager;
                return;
            }
        }

        throw new Exception("Couldn't initialize");
    }

    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        pkixTrustManager.checkServerTrusted(chain, authType);
    }

    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        pkixTrustManager.checkServerTrusted(chain, authType);
    }

    public X509Certificate[] getAcceptedIssuers() {
        return pkixTrustManager.getAcceptedIssuers();
    }
}

现在,使用这个信任管理器,我必须创建一个套接字工厂:
public class ValicertSSLProtocolSocketFactory implements ProtocolSocketFactory {

    private SSLContext sslContext = null;

    public ValicertSSLProtocolSocketFactory() {
        super();
    }

    private static SSLContext createValicertSSLContext() {
        try {
            ValicertX509TrustManager valicertX509TrustManager = new ValicertX509TrustManager();
            SSLContext context = SSLContext.getInstance("TLS");
            context.init(null, new ValicertX509TrustManager[] { valicertX509TrustManager}, null);
            return context;
        }

        catch(Exception e) {
            Log.error(Log.Context.Net, e);
            return null;
        }
    }

    private SSLContext getSSLContext() {
        if(this.sslContext == null) {
            this.sslContext = createValicertSSLContext();
        }

        return this.sslContext;
    }

    public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException {
        return getSSLContext().getSocketFactory().createSocket(host, port, clientHost, clientPort);
    }

    public Socket createSocket(final String host, final int port, final InetAddress localAddress, final int localPort, final HttpConnectionParams params) throws IOException {
        if(params == null) {
            throw new IllegalArgumentException("Parameters may not be null");
        }

        int timeout = params.getConnectionTimeout();
        SocketFactory socketFactory = getSSLContext().getSocketFactory();

        if(timeout == 0) {
            return socketFactory.createSocket(host, port, localAddress, localPort);
        }

        else {
            Socket socket = socketFactory.createSocket();
            SocketAddress localAddr = new InetSocketAddress(localAddress, localPort);
            SocketAddress remoteAddr = new InetSocketAddress(host, port);
            socket.bind(localAddr);
            socket.connect(remoteAddr, timeout);
            return socket;
        }
    }

    public Socket createSocket(String host, int port) throws IOException {
        return getSSLContext().getSocketFactory().createSocket(host, port);
    }

    public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
        return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);
    }

    public boolean equals(Object obj) {
        return ((obj != null) && obj.getClass().equals(ValicertSSLProtocolSocketFactory.class));
    }

    public int hashCode() {
        return ValicertSSLProtocolSocketFactory.class.hashCode();
    }
}

然后我刚刚注册了一个新的协议:

Protocol.registerProtocol("vhttps", new Protocol("vhttps", new ValicertSSLProtocolSocketFactory(), 443));
PostMethod postMethod = new PostMethod(url);
for (Map.Entry<String, String> entry : params.entrySet()) {
    postMethod.addParameter(entry.getKey(), StringUtils.Nz(entry.getValue()));
}

HttpClient client = new HttpClient();
int status = client.executeMethod(postMethod);
if (status == 200) {
    StringBuilder resultBuffer = new StringBuilder();
    resultBuffer.append(postMethod.getResponseBodyAsString());
    return new HttpResponse(resultBuffer.toString(), "");
} else {
    throw new IOException("Invalid response code: " + status);
}

唯一的缺点是我必须为这个特定的证书创建一个特定的协议(vhttps)。

很难相信所有这些都是必要的。所有这一切都带有一种明显的不理解根本问题的味道。 - user207421
@EJP 不需要这么居高临下。我非常清楚这个解决方案并不是最佳的。如果你有更好的解决方案,我很乐意听取。我尝试了许多方法来使它工作,包括将证书(我甚至确保可以追踪到完整的信任链)添加到服务器的cacerts文件中。似乎什么都没有起作用。在最终使用这个解决方案之前,我对这个问题进行了大量研究。这不是我的首选。 - Vivin Paliath
@Vivin - 感谢您的建议,但我真的希望不必采取这种方法。 :) 许多其他代码不受我直接控制,必须进行更改,因此这是一种情况,我一直希望我的一方或机器配置方面出现错误。 - Bill
@Bill 这真的很奇怪!当我遇到这个问题时,我尝试了许多方法,但似乎没有什么作用。我们甚至在生产机器上添加了证书,但也没有效果。 - Vivin Paliath
@EJP,您提供了一个替代方案来直接解决问题,这样做非常好。 - Vivin Paliath
显示剩余2条评论

0

我猜可能发生了以下两种情况之一:

a) 你在一个Web服务器上运行代码。它们通常使用自己的信任存储库 - 所以你真的确定在执行代码时使用的是cacerts吗?

b) 默认情况下,Java会尝试通过下载和解释CRL来检查证书的有效性。如果你在代理后面,下载将失败,因此整个PKIX检查也会失败。


1
由于他直接安装服务器证书,因此(b)不适用。除此之外,答案很好。 - user207421
@EJP 哎呀,你说得对,我把它和导入根证书混淆了。我会修复的。 - emboss
起初我以为A就是答案,但当我将其简化为仅包含完整调试的小样本代码时,它仍然失败了,让我感到沮丧。不过还是感谢你们的建议。 - Bill

0

SSL 调试跟踪将显示您正在使用哪个 cacerts 文件,只要您不手动加载它。显然,您没有使用您认为的那个。


检查的想法不错,但并非如此:“trustStore is: /usr/lib/jvm/java-1.6.0-sun-1.6.0.20.x86_64/jre/lib/security/jssecacerts”。正如我所提到的,我已经尝试过使用jssecacerts和cacerts,其中包括通过trustStore设置强制路径。 - Bill

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