我必须使用无效的SSL证书调用托管在Web服务器上的HTTP服务。在开发中,我使用 keytool 导入证书,但每个客户端安装的证书都不同,所以我不能将其捆绑。
前言:我 DO 知道跳过SSL验证真的很难看。在这种特定情况下,我甚至不需要SSL,系统中的所有其他通信都是简单的HTTP。所以我真的不关心MITM攻击等。攻击者无需破解SSL,因为数据没有SSL。这是对我无法控制的遗留系统的支持。
我正在使用HttpURLConnection
SSLSocketFactory
,NaiveTrustManager
和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)
我的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;
}
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;
}
}
}
用法如下:
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;
}
当我在搜索错误消息时,大多数人只是在他们的商店中导入证书但是我再也不能这样做,因为我不知道它将是哪个证书。如果这不起作用,我唯一的选择是创建一个可以下载证书并以更简单的方式添加它的工具,但是我宁愿让我的Java代码忽略无效的证书。
有什么想法吗?
答案 0 :(得分:2)
上面的代码实际上没有任何问题。问题似乎在于Weblogic和这个Certicom TLS模块。当我查看服务器选项, SSL 和高级时,我看到我可以指定自定义HostnameVerifier(SSLMBean.HostnameVerifier)但唯一的元素建议不要使用干扰证书验证的能力。
我在Weblogic之外尝试了上面的代码并且它运行得很漂亮(虽然修复了帖子中的HostnameVerifier)。
然后我尝试将“-DUseSunHttpHandler = true”添加到ipolevoy在this other question中建议的Weblogic参数中。它开始运作了。
话虽这么说,在Oracle Service Bus服务器上切换HTTP处理程序似乎有点冒险。可能会有几个星期后再次让我感到困惑的副作用......
我还尝试定义自己的trustStore并将其指向包含所需密钥的jssecacert。 Weblogic也忽略了它,因为它为每个服务器都有自己的trustStore设置。所以我要求管理员手动导入所需的密钥或将Weblogic指向我自己的商店。
答案 1 :(得分:0)
实际上,这是10.3.5以下的Weblogic版本中的一个已知错误,其中有一个可从Oracle获得的补丁。有关详细信息,请参阅My Oracle Support中的文档1474989.1。
上述修复是Oracle推荐的(但受支持的)解决方法,可以使用,但不是首选解决方案。
首选解决方案是下载Oracle文章中提到的补丁,并将SSL主机名验证程序替换为新的一个,它也是Weblogic 10.3.5及更高版本的一部分。如果您希望在支持方面与Oracle保持一致,那么这就是您的选择。