Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/339.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 如何在特定连接上使用不同的证书?_Java_Ssl_Keystore_Truststore_Jsse - Fatal编程技术网

Java 如何在特定连接上使用不同的证书?

Java 如何在特定连接上使用不同的证书?,java,ssl,keystore,truststore,jsse,Java,Ssl,Keystore,Truststore,Jsse,我添加到我们的大型Java应用程序中的一个模块必须与另一家公司的SSL安全网站进行对话。问题是该站点使用自签名证书。我有一份证书的副本,以验证我没有遇到中间人攻击,我需要将此证书合并到我们的代码中,以便成功连接到服务器 以下是基本代码: void sendRequest(String dataPacket) { String urlStr = "https://host.example.com/"; URL url = new URL(urlStr); HttpURLConnecti

我添加到我们的大型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应用程序以接受自签名证书的首选、标准或最佳方法是什么?我能完成上面提到的所有目标吗,还是我必须妥协?是否有一个选项涉及文件、目录和配置设置,几乎没有代码?

我们复制JRE的信任库并将自定义证书添加到该信任库,然后告诉应用程序使用带有系统属性的自定义信任库。这样,我们就不需要使用默认的JRE信任库

缺点是,当您更新JRE时,它的新信任库不会自动与您的自定义信任库合并

您可以使用安装程序或启动例程来验证信任库/jdk并检查不匹配或自动更新信任库,从而处理这种情况。如果在应用程序运行时更新信任库,我不知道会发生什么


此解决方案并非100%优雅或万无一失,但它简单、有效且不需要代码。

自己创建一个
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
编写自己的代码,或者仅使用JDK中的
keytool
导入它(keytool不适用于“密钥条目”,但适用于“受信任条目”)


在使用commons httpclient访问具有自签名证书的内部https服务器时,我必须这样做。是的,我们的解决方案是创建一个自定义TrustManager,它只传递所有信息(记录调试消息)

这归结为拥有我们自己的SSLSocketFactory,它从本地SSLContext创建SSL套接字,该SSLContext设置为只有本地TrustManager与之关联。您根本不需要靠近密钥库/证书库

这是在我们的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是前面提到的虚拟信任管理器实现。

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

  • 检索公钥:
    $openssl s_client-连接dev server:443
    ,然后创建一个文件dev-server.pem,如下所示

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

  • 重启JVM


  • 来源:

    为了解决这个问题,我在网上浏览了很多地方。 这是我为使其工作而编写的代码:

    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-----";
    

    我已经测试过,如果证书字符串是自签名的,您可以将任何字符放入证书字符串中,只要您保持上面的确切结构。我通过笔记本电脑的终端命令行获得了证书字符串。

    非常感谢!其他人帮我指出了正确的方向,但最终你的方法是我采取的。我有一项繁重的任务,将PEM证书文件转换为JKS Java密钥库文件,我在这里找到了帮助:我很高兴它成功了。我很抱歉你在钥匙店挣扎;我应该把它包括在我的答案里。认证工厂并不难。事实上,我想我会为以后的任何人做一个更新。这些代码所做的只是复制您可以通过设置JSSE Refernence指南中描述的三个系统属性来完成的功能。@EJP:这是很久以前的事了,所以我不记得了,但我猜基本原理是在“大型Java应用程序”中,可能会建立其他HTTP连接。设置全局属性可能会干扰工作连接,或允许这一方欺骗服务器。这是一个在个案基础上使用内置信任管理器的示例。正如OP所说,
    static public String certificateString=
            "-----BEGIN CERTIFICATE-----\n" +
            "MIIGQTCCBSmgAwIBAgIHBcg1dAivUzANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UE" +
            "BhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsTIlNlY3VyZSBE" +
            ... a bunch of characters...
            "5126sfeEJMRV4Fl2E5W1gDHoOd6V==\n" +
            "-----END CERTIFICATE-----";