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

ski*_*ppy 159 java ssl jsse keystore truststore

我正在添加到我们的大型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();
}
Run Code Online (Sandbox Code Playgroud)

在没有为自签名证书进行任何额外处理的情况下,这会在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
Run Code Online (Sandbox Code Playgroud)

理想情况下,我的代码需要教Java接受这个自签名证书,对于应用程序中的这一点,以及其他任何地方.

我知道我可以将证书导入JRE的证书颁发机构商店,这将允许Java接受它.如果我能提供帮助,那不是我想采取的方法; 对于他们可能不使用的一个模块,我们所有客户的机器上看起来非常具有侵入性; 它会影响使用相同JRE的所有其他Java应用程序,我不喜欢它,即使访问此站点的任何其他Java应用程序的几率为零.这也不是一个简单的操作:在UNIX上,我必须获得以这种方式修改JRE的访问权限.

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

设置Java应用程序以接受自签名证书的首选,标准或最佳方法是什么?我可以完成上面提到的所有目标,还是我必须妥协?是否有一个涉及文件和目录和配置设置的选项,以及几乎没有代码?

eri*_*son 162

SSLSocket自己创建一个工厂,并HttpsURLConnection在连接之前设置它.

...
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
conn.setSSLSocketFactory(sslFactory);
conn.setMethod("POST");
...
Run Code Online (Sandbox Code Playgroud)

你想要创建一个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();
Run Code Online (Sandbox Code Playgroud)

如果您在创建密钥库时需要帮助,请发表评论.


这是加载密钥库的示例:

KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(trustStore, trustStorePassword);
trustStore.close();
Run Code Online (Sandbox Code Playgroud)

要使用PEM格式证书创建密钥库,您可以使用JDK 编写自己的代码CertificateFactory,或者只使用keytoolJDK 导入代码(keytool 不适用于"密钥条目",但对于"受信任的条目"来说很合适).

keytool -import -file selfsigned.pem -alias server -keystore server.jks
Run Code Online (Sandbox Code Playgroud)

  • 非常感谢你!其他人帮助指出了我正确的方向,但最终你的方法是我采取的方法.我有一项将PEM证书文件转换为JKS Java密钥库文件的艰巨任务,我在这里找到了帮助:http://stackoverflow.com/questions/722931/ssl-socket-php-code-needs-to-被转换到Java (3认同)
  • 我很高兴事情成功了。很抱歉您在钥匙存储方面遇到了困难;我应该将其包含在我的答案中。使用CertificateFactory 并不太困难。事实上,我想我会为后来的人做更新。 (2认同)
  • @EJP:这是不久前的,所以我不记得了,但我猜测的理由是,在"大型Java应用程序"中,可能会建立其他HTTP连接.设置全局属性可能会干扰工作连接,或允许此一方欺骗服务器.这是根据具体情况使用内置信任管理器的示例. (2认同)
  • 正如OP所说,"我的代码需要教Java接受这一个自签名证书,对于应用程序中的这一点,而不是其他任何地方." (2认同)

Jos*_*osh 18

我在线阅读了很多地方来解决这个问题.这是我为使其工作而编写的代码:

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());
Run Code Online (Sandbox Code Playgroud)

app.certificateString是包含证书的String,例如:

static public String certificateString=
        "-----BEGIN CERTIFICATE-----\n" +
        "MIIGQTCCBSmgAwIBAgIHBcg1dAivUzANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UE" +
        "BhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsTIlNlY3VyZSBE" +
        ... a bunch of characters...
        "5126sfeEJMRV4Fl2E5W1gDHoOd6V==\n" +
        "-----END CERTIFICATE-----";
Run Code Online (Sandbox Code Playgroud)

我已经测试过你可以在证书字符串中放置任何字符,如果它是自签名的,只要你保持上面的确切结构.我用笔记本电脑的终端命令行获取了证书字符串.

  • 感谢分享@Josh。我创建了一个小型 Github 项目来演示您正在使用的代码:https://github.com/aasaru/ConnectToTrustedServerExample (2认同)

use*_*322 13

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

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

    -----BEGIN CERTIFICATE----- 
    lklkkkllklklklklllkllklkl
    lklkkkllklklklklllkllklkl
    lklkkkllklk....
    -----END CERTIFICATE-----
    
    Run Code Online (Sandbox Code Playgroud)
  2. 导入密钥:#keytool -import -alias dev-server -keystore $JAVA_HOME/jre/lib/security/cacerts -file dev-server.pem.密码:的changeit

  3. 重启JVM

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


Mr.*_* 安宇 12

我们复制JRE的信任库并将我们的自定义证书添加到该信任库,然后告诉应用程序将自定义信任库与系统属性一起使用.这样我们就可以单独保留默认的JRE信任库.

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

您可以通过安装程序或启动例程来验证truststore/jdk并检查不匹配或自动更新信任库来处理此方案.我不知道如果在应用程序运行时更新信任库会发生什么.

这个解决方案不是100%优雅或万无一失,但它简单,有效,并且不需要代码.


ara*_*nid 12

当使用commons-httpclient访问带有自签名证书的内部https服务器时,我不得不做这样的事情.是的,我们的解决方案是创建一个只传递所有内容的自定义TrustManager(记录调试消息).

这归结为我们自己的SSLSocketFactory从我们的本地SSLContext创建SSL套接字,该套接字被设置为只与我们的本地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);
}
Run Code Online (Sandbox Code Playgroud)

与实现SecureProtocolSocketFactory的其他方法一起.LocalSSLTrustManager是前面提到的虚拟信任管理器实现.

  • 如果您禁用所有信任验证,那么首先使用SSL/TLS几乎没有意义.在本地测试是可以的,但如果你想在外面连接则不行. (8认同)