我添加到大型 Java 应用程序中的模块必须与另一家公司的 SSL 安全网站进行通信。问题是该网站使用自签名证书。我有一份证书副本来验证我没有遇到中间人攻击,并且我需要将此证书合并到我们的代码中,以便成功连接到服务器。

这是基本代码:

void sendRequest(String dataPacket) {
  String urlStr = "https://host.example.com/";
  URL url = new URL(urlStr);
  HttpURLConnection conn = (HttpURLConnection)url.openConnection();
  conn.setMethod("POST");
  conn.setRequestProperty("Content-Length", data.length());
  conn.setDoOutput(true);
  OutputStreamWriter o = new OutputStreamWriter(conn.getOutputStream());
  o.write(data);
  o.flush();
}

如果没有对自签名证书进行任何额外的处理,则会在 conn.getOutputStream() 处终止,但出现以下异常:

Exception in thread "main" 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
....
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
....
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

理想情况下,我的代码需要教 Java 接受这个自签名证书,用于应用程序中的这一点,而不是其他地方。

我知道我可以将证书导入到 JRE 的证书颁发机构存储中,这将允许 Java 接受它。如果我能提供帮助的话,我不想采取这种方法;在我们所有客户的机器上对他们可能不使用的一个模块进行操作似乎非常具有侵入性;它会影响使用相同 JRE 的所有其他 Java 应用程序,尽管任何其他 Java 应用程序访问此站点的可能性为零,但我不喜欢这样。这也不是一个简单的操作:在 UNIX 上,我必须通过这种方式获得修改 JRE 的访问权限。

我还发现我可以创建一个 TrustManager 实例来执行一些自定义检查。看起来我什至可以创建一个 TrustManager,在除此证书之外的所有实例中委托给真正的 TrustManager。但看起来 TrustManager 是全局安装的,我认为会影响我们应用程序的所有其他连接,这对我来说也不太对劲。

设置 Java 应用程序以接受自签名证书的首选、标准或最佳方法是什么?我能否实现上述所有目标,还是必须做出妥协?是否有涉及文件、目录和配置设置以及几乎没有代码的选项?

有帮助吗?

解决方案

创建SSLSocket工厂自己,并在连接前将其设置在HttpsURLConnection

...
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
conn.setSSLSocketFactory(sslFactory);
conn.setMethod("POST");
...

您需要创建一个SSLSocketFactory并保持周围。这里是一个草图如何初始化:

/* Load the keyStore that includes self-signed cert as a "trusted" entry. */
KeyStore keyStore = ... 
TrustManagerFactory tmf = 
  TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, tmf.getTrustManagers(), null);
sslFactory = ctx.getSocketFactory();

如果您需要帮助创建密钥存储,请评论。


下面是装载密钥存储的示例:

KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(trustStore, trustStorePassword);
trustStore.close();

要创建一个PEM格式的证书密钥存储,您可以使用CertificateFactory编写自己的代码,或只是keytool导入从JDK(密钥工具的不会的一个“关键的录入工作”,但仅仅是对于精细‘可信任条目’)。

keytool -import -file selfsigned.pem -alias server -keystore server.jks

其他提示

我经历的很多地方在线阅读来解决这件事情。 这是我写的,使其工作的代码:

                ByteArrayInputStream derInputStream = new ByteArrayInputStream(app.certificateString.getBytes());
                CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
                X509Certificate cert = (X509Certificate) certificateFactory.generateCertificate(derInputStream);
                String alias = "alias";//cert.getSubjectX500Principal().getName();

                KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
                trustStore.load(null);
                trustStore.setCertificateEntry(alias, cert);
                KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
                kmf.init(trustStore, null);
                KeyManager[] keyManagers = kmf.getKeyManagers();

                TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
                tmf.init(trustStore);
                TrustManager[] trustManagers = tmf.getTrustManagers();

                SSLContext sslContext = SSLContext.getInstance("TLS");
                sslContext.init(keyManagers, trustManagers, null);
                URL url = new URL(someURL);
                conn = (HttpsURLConnection) url.openConnection();
                conn.setSSLSocketFactory(sslContext.getSocketFactory());

app.certificateString是包含证书,一个字符串例如:

            static public String certificateString=
            "-----BEGIN CERTIFICATE-----\n" +
            "MIIGQTCCBSmgAwIBAgIHBcg1dAivUzANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UE" +
            "BhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsTIlNlY3VyZSBE" +
            ... a bunch of characters...
            "5126sfeEJMRV4Fl2E5W1gDHoOd6V==\n" +
            "-----END CERTIFICATE-----";

我已经测试过,你可以把任何字符的字符串,证书,如果是自签名的,只要你保持上述的确切结构。我得到我的笔记本电脑的终端的命令行的证书字符串。

如果创建一个 SSLSocketFactory 不是一个选项,只需将密钥导入 JVM

  1. 检索公钥:$openssl s_client -connect dev-server:443, ,然后创建一个文件 开发服务器.pem 看起来像

    -----BEGIN CERTIFICATE----- 
    lklkkkllklklklklllkllklkl
    lklkkkllklklklklllkllklkl
    lklkkkllklk....
    -----END CERTIFICATE-----
    
  2. 导入密钥: #keytool -import -alias dev-server -keystore $JAVA_HOME/jre/lib/security/cacerts -file dev-server.pem。密码: 更改

  3. 重启JVM

来源: 如何解决javax.net.ssl.SSLHandshakeException?

我们复制JRE的信任,并添加我们的自定义证书到信任,然后告诉应用程序使用自定义的信任与系统性能。这样,我们保留默认JRE信任一个人。

缺点是,当你将JRE更新你没有自动获得其新的信任与您的自定义一个合并。

您也许可以由具有安装或启动程序是用于验证不匹配的信任/ JDK和支票或自动更新信任处理这种情况。我不知道,如果你的应用程序运行时更新信任发生了什么。

此溶液是不是100%或优雅万无一失但它的简单,有效,并且不需要代码。

我不得不使用时,做这样的事情公地httpclient的使用自签名证书来访问内部HTTPS服务器。是的,我们的解决方案是创建一个简单的通过一切(记录调试消息)定制的TrustManager。

这可以归结为有我们自己的SSLSocketFactory从我们当地的SSLContext,它被设置为具有关联只是我们本地的TrustManager创建SSL套接字。你不需要去附近一个密钥/的CertStore的。

所以,这是我们LocalSSLSocketFactory:

static {
    try {
        SSL_CONTEXT = SSLContext.getInstance("SSL");
        SSL_CONTEXT.init(null, new TrustManager[] { new LocalSSLTrustManager() }, null);
    } catch (NoSuchAlgorithmException e) {
        throw new RuntimeException("Unable to initialise SSL context", e);
    } catch (KeyManagementException e) {
        throw new RuntimeException("Unable to initialise SSL context", e);
    }
}

public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
    LOG.trace("createSocket(host => {}, port => {})", new Object[] { host, new Integer(port) });

    return SSL_CONTEXT.getSocketFactory().createSocket(host, port);
}

随着实施SecureProtocolSocketFactory其它方法。 LocalSSLTrustManager是上述虚设信任管理器实现。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top