忽略 Java 中的 SSL 验证
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/13470998/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Ignoring SSL validation in Java
提问by Eric Darchis
I have to call an HTTP service hosted on web server with an invalid SSL certificate. In dev, I'm importing the certificate with keytoolbut the certificate will be different on each client install, so I can't just bundle it.
我必须使用无效的 SSL 证书调用托管在 Web 服务器上的 HTTP 服务。在开发中,我使用keytool导入证书,但每个客户端安装的证书都会不同,所以我不能只是捆绑它。
Foreword: I DOknow that skipping SSL validation is really ugly. In this specific case, I would not even need SSL and all other communications in the system are over simple HTTP. So I really don't care about MITM attacks or such. An attacker would not need to go as far as to break SSL because there is no SSL for the data. This is support for a legacy system over which I have no control.
前言:我不要知道跳过SSL验证实在是太丑了。在这种特定情况下,我什至不需要 SSL,系统中的所有其他通信都通过简单的 HTTP。所以我真的不关心 MITM 攻击之类的。攻击者不需要破坏 SSL,因为数据没有 SSL。这是对我无法控制的遗留系统的支持。
I'm using HttpURLConnection
with an SSLSocketFactory
that has a NaiveTrustManager
and a NaiveHostnameVerifier
. This works on some self-signed servers I tried but not on the customer's site. The error I'm getting is:
我正在使用HttpURLConnection
带有SSLSocketFactory
aNaiveTrustManager
和 a 的NaiveHostnameVerifier
。这适用于我尝试过但不适用于客户站点的一些自签名服务器。我得到的错误是:
javax.net.ssl.SSLKeyException: [Security:090477]Certificate chain received from xxxxxxxxxx was not trusted causing SSL handshake failure.
at com.certicom.tls.interfaceimpl.TLSConnectionImpl.fireException(Unknown Source)
at com.certicom.tls.interfaceimpl.TLSConnectionImpl.fireAlertSent(Unknown Source)
at com.certicom.tls.record.handshake.HandshakeHandler.fireAlert(Unknown Source)
at com.certicom.tls.record.handshake.HandshakeHandler.fireAlert(Unknown Source)
at com.certicom.tls.record.handshake.ClientStateReceivedServerHello.handle(Unknown Source)
at com.certicom.tls.record.handshake.HandshakeHandler.handleHandshakeMessage(Unknown Source)
at com.certicom.tls.record.handshake.HandshakeHandler.handleHandshakeMessages(Unknown Source)
at com.certicom.tls.record.MessageInterpreter.interpretContent(Unknown Source)
at com.certicom.tls.record.MessageInterpreter.decryptMessage(Unknown Source)
at com.certicom.tls.record.ReadHandler.processRecord(Unknown Source)
at com.certicom.tls.record.ReadHandler.readRecord(Unknown Source)
at com.certicom.tls.record.ReadHandler.readUntilHandshakeComplete(Unknown Source)
at com.certicom.tls.interfaceimpl.TLSConnectionImpl.completeHandshake(Unknown Source)
at com.certicom.tls.record.WriteHandler.write(Unknown Source)
at com.certicom.io.OutputSSLIOStreamWrapper.write(Unknown Source)
at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:65)
at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:123)
at java.io.FilterOutputStream.flush(FilterOutputStream.java:123)
at weblogic.net.http.HttpURLConnection.writeRequests(HttpURLConnection.java:154)
at weblogic.net.http.HttpURLConnection.getInputStream(HttpURLConnection.java:358)
at weblogic.net.http.SOAPHttpsURLConnection.getInputStream(SOAPHttpsURLConnection.java:37)
at weblogic.net.http.HttpURLConnection.getResponseCode(HttpURLConnection.java:947)
at (my own code)
My SimpleSocketFactory
looks like:
我的SimpleSocketFactory
样子:
public static final SSLSocketFactory getSocketFactory()
{
if ( sslSocketFactory == null ) {
try {
// get ssl context
SSLContext sc = SSLContext.getInstance("SSL");
// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[]{
new NaiveTrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
log.debug("getAcceptedIssuers");
return new java.security.cert.X509Certificate[0];
}
public void checkClientTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
log.debug("checkClientTrusted");
}
public void checkServerTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
log.debug("checkServerTrusted");
}
}
};
sc.init(null, trustAllCerts, new java.security.SecureRandom());
// EDIT: fixed the following line that was redeclaring SSLSocketFactory sslSocketFactory, returning null every time. Same result though.
sslSocketFactory = sc.getSocketFactory();
HttpsURLConnection.setDefaultSSLSocketFactory(sslSocketFactory);
// EDIT: The following line has no effect
//HttpsURLConnection.setDefaultHostnameVerifier(new NaiveHostNameVerifier());
} catch (KeyManagementException e) {
log.error ("No SSL algorithm support: " + e.getMessage(), e);
} catch (NoSuchAlgorithmException e) {
log.error ("Exception when setting up the Naive key management.", e);
}
}
return sslSocketFactory;
}
The NaiveHostnameVerifier
has a way to limit the valid hosts but it's left null, so basically accepting anything:
该NaiveHostnameVerifier
有办法限制的有效主机,但它保留为空,所以基本上任何接受:
public class NaiveHostnameVerifier implements HostnameVerifier {
String[] patterns;
public NaiveHostnameVerifier () {
this.patterns=null;
}
public NaiveHostnameVerifier (String[] patterns) {
this.patterns = patterns;
}
public boolean verify(String urlHostName,SSLSession session) {
if (patterns==null || patterns.length==0) {
return true;
} else {
for (String pattern : patterns) {
if (urlHostName.matches(pattern)) {
return true;
}
}
return false;
}
}
}
The usage is like this:
用法是这样的:
try {
conn = (HttpURLConnection)url.openConnection();
if (conn instanceof HttpsURLConnection) {
((HttpsURLConnection)conn).setSSLSocketFactory(SimpleSSLSocketFactory.getSocketFactory());
// EDIT: added this line, the HV has to be set on connection, not on the factory.
((HttpsURLConnection)conn).setHostnameVerifier(new NaiveHostnameVerifier());
}
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-type","application/x-www-form-urlencoded");
conn.connect();
StringBuffer sbContent = new StringBuffer();
// (snip)
DataOutputStream stream = new DataOutputStream(conn.getOutputStream ());
stream.writeBytes(sbContent.toString());
stream.flush();
stream.close();
} catch (ClassCastException e) {
log.error("The URL does not seem to point to a HTTP connection");
return null;
} catch (IOException e) {
log.error("Error accessing the requested URL", e);
return null;
}
When I'm searching on the error message, most people just import the certificate in their store but again, I can't really do that because I don't know which certificate it'll be. My only alternative if this doesn't work is to make a tool that can download the certificate and add it in an easier way that cryptic command lines but I'd rather let my Java code just ignore the invalid certificate.
当我搜索错误消息时,大多数人只是在他们的商店中导入证书,但同样,我真的不能这样做,因为我不知道它将是哪个证书。如果这不起作用,我唯一的选择是制作一个可以下载证书并以更简单的方式添加证书的工具,而不是神秘的命令行,但我宁愿让我的 Java 代码忽略无效的证书。
Any idea ?
任何的想法 ?
采纳答案by Eric Darchis
There is in fact nothing wrong with the code above. The problem seems to lie with Weblogic and this Certicom TLS module. When I look at the serveroptions, SSLand AdvancedI see that I can specify a custom HostnameVerifier (SSLMBean.HostnameVerifier) but the only element suggesting the ability to interfere with Certificate validation is deprecated.
上面的代码实际上没有任何问题。问题似乎出在 Weblogic 和这个 Certicom TLS 模块上。当我查看服务器选项、SSL和Advanced 时,我发现我可以指定自定义 HostnameVerifier (SSLMBean.HostnameVerifier),但建议干扰证书验证能力的唯一元素已被弃用。
I tried the above code outside of Weblogic and it worked beautifully (fixed the HostnameVerifier in the post though).
我在 Weblogic 之外尝试了上面的代码,它运行得很好(虽然在帖子中修复了 HostnameVerifier)。
Then I tried to add "-DUseSunHttpHandler=true" to the Weblogic parameters as suggested by ipolevoy in this other question. It started working.
然后我尝试按照 ipolevoy 在另一个问题中的建议,将“-DUseSunHttpHandler=true”添加到 Weblogic 参数中。它开始工作了。
That being said, switching the HTTP handler on an Oracle Service Bus server seems a bit risky. There might well be side-effects that come back to bite me in a few weeks time...
话虽如此,在 Oracle Service Bus 服务器上切换 HTTP 处理程序似乎有点冒险。几周后很可能会有副作用再次咬我……
I also attempted to define my own trustStore and point it to a jssecacert that contained the required key. It was also ignored by Weblogic because it has its own setting of the trustStore for each server. So I'm resorting to ask the administrator to manually import the required keys or point Weblogic to my own store.
我还尝试定义自己的 trustStore 并将其指向包含所需密钥的 jssecacert。Weblogic 也忽略了它,因为它对每个服务器都有自己的 trustStore 设置。所以我求助于要求管理员手动导入所需的密钥或将 Weblogic 指向我自己的商店。
回答by Ajunne
Actually, this is a know bug in Weblogic versions below 10.3.5, for which there is a patch available from Oracle. Please see document 1474989.1 in My Oracle Support for details.
实际上,这是 10.3.5 以下 Weblogic 版本中的一个已知错误,Oracle 提供了一个补丁。有关详细信息,请参阅 My Oracle Support 中的文档 1474989.1。
The fix above is a non-recommended (but supported) workaround by Oracle, which will work, but is not the preferred solution.
上面的修复是 Oracle 不推荐(但支持)的解决方法,它可以工作,但不是首选解决方案。
The preferred solution is to download the patch mentioned in the Oracle article, and replace the SSL hostname verifier with the new one which is also part of Weblogic 10.3.5 and above. If you wish to remain compliant with Oracle in terms of support, this is the way to go.
首选的解决方案是下载 Oracle 文章中提到的补丁,并将 SSL 主机名验证器替换为新的,它也是 Weblogic 10.3.5 及更高版本的一部分。如果您希望在支持方面与 Oracle 保持一致,这就是您要走的路。