【问题标题】:Java SSL - InstallCert recognizes certificate, but still "unable to find valid certification path" error?Java SSL - InstallCert 识别证书,但仍然“无法找到有效的证书路径”错误?
【发布时间】:2012-06-20 16:12:05
【问题描述】:

以为我会遇到与其他人相同的问题,我已经经历了许多类似的问题和潜在的解决方案,但没有运气。

我使用的信任库是 cacerts,位于 Java 1.6.0 JRE 的 lib/security 中(构建 1.6.0_20-b02...这可能是问题的根源吗?)。我也尝试过 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'ing 等)给我“这已经存在于这里的另一个别名下" 消息类型。因此,证书进入工具的方式似乎没有任何问题。

在代码中强制显式 trustStore 路径没有任何区别,在所有情况下,当我打开调试(通过 javax.net.debug 的 setProperty 到“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 - 我要解决的核心问题是在我不直接控制的应用程序层中(在这种情况下,Grails 应用程序调用了一些最近成为仅 HTTPS 的 Web 服务) ,所以虽然我可以在自己的本地场景中使用它,但我无法在上游强制使用它。这很痛苦,但我稍后会处理它 - 现在,如果我可以让它在最细粒度的级别上工作,它至少会告诉我在解决方案链中下一步该去哪里。
  • 您是否在像 GlassFish 这样的应用服务器下运行它?您可能会检查该进程是否正在运行 /usr/lib/jvm/java-1.6.0-sun-1.6.0.20.x86_64 中的 Java 二进制文件,而不是其他不喜欢您的 trustStore 的 Java 二进制文件(OpenJDK,等等)。您发布的调试输出很有帮助,您可以发布更多吗?

标签: ssl ssl-certificate java


【解决方案1】:

您仍然可以通过实施自己的信任管理器来检查证书。我遇到了类似的问题here。我还尝试将证书添加到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)。

【讨论】:

  • 很难相信所有这些都是真正必要的。有一种不理解这一切的根本问题的独特味道。
  • @EJP 没必要这么傲慢。我痛苦地意识到这个解决方案不是最优的。如果您有更好的解决方案,我很高兴听到它。我尝试了很多方法来让它工作,包括将证书(我什至确保我可以一直跟踪信任链)添加到服务器的 cacerts 文件中。似乎没有任何效果。在最终使用此解决方案之前,我对这个问题进行了很多研究。这不是我的第一选择。
  • @Vivin - 感谢这个建议,虽然我真的希望我不必走这条路。 :) 必须更改许多我不直接控制的其他代码,所以这是我一直希望在我这边或机器配置方面的某个地方出现错误的情况之一。
  • @Bill 没问题;乐意效劳!我同意,这个问题非常令人沮丧!希望你能找到解决办法。
  • @Vivin 我终于找到了解决方案 - 我还不完全理解它,所以我正在进一步挖掘。发生的情况是,当我通过 keytool 或 Portecle 在此特定机器上创建或更新 cacerts 文件时,我看到了失败,但是当我在另一台机器上创建 cacerts 并替换此主机上的 cacerts 文件时,它可以工作!太奇怪了。这似乎表明我在该主机上遇到了 lib 或版本问题,但我仍然对为什么来自调试 / 等的所有其他指示都显示为“是的,一切都很好!”感到困惑。一旦我有更确凿的理由,我会更新以防它帮助其他人。
【解决方案2】:

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

【讨论】:

  • 好想检查一下,但不是这样:“trustStore 是:/usr/lib/jvm/java-1.6.0-sun-1.6.0.20.x86_64/jre/lib/security/jssecacerts ”。正如我所提到的,我已经使用 jssecacerts 和 cacerts 进行了尝试,其中包括通过 trustStore 设置强制执行路径。
【解决方案3】:

我的猜测是发生了以下两种情况:

a) 您在网络服务器上运行您的代码。他们经常使用自己的信任库 - 所以您确定执行代码时使用的是 cacerts?

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

【讨论】:

  • 因为他是直接安装服务器证书,所以(b)不适用。否则很好回答。
  • @EJP 哎呀,你说得对,我把它和导入根证书混在一起了。会解决的。
  • 起初我以为是 A,然后当我把它归结为带有完整调试的微小示例代码时,它仍然失败了,我很沮丧。不过感谢您的建议。
猜你喜欢
  • 1970-01-01
  • 2017-09-19
  • 1970-01-01
  • 2017-07-20
  • 2013-05-24
  • 1970-01-01
  • 2014-05-26
  • 2020-03-01
  • 1970-01-01
相关资源
最近更新 更多