我必须使用无效的SSL证书调用Web服务器上托管的HTTP服务。在开发人员中,我使用 keytool 导入证书,但是证书在每个客户端安装中都会有所不同,因此我不能将其捆绑在一起。
前言:我 不要 知道跳过SSL验证实在是太丑了。在这种特定情况下,我什至不需要SSL,并且系统中的所有其他通信都是通过简单HTTP进行的。所以我真的不在乎MITM攻击之类。攻击者无需破坏SSL,因为数据没有SSL。这是对我无法控制的旧版系统的支持。
我使用的是HttpURLConnection与SSLSocketFactory具有NaiveTrustManager和NaiveHostnameVerifier。这在我尝试过的某些自签名服务器上有效,但在客户站点上无效。我得到的错误是:
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样子是:
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代码只忽略无效的证书。
任何想法 ?
实际上,上面的代码没有错。问题似乎在于Weblogic和此Certicom TLS模块。当我查看 服务器 选项, SSL 和 高级时, 我看到可以指定自定义HostnameVerifier(SSLMBean.HostnameVerifier),但是建议干扰证书验证功能的唯一元素已被弃用。
我在Weblogic之外尝试了上面的代码,并且效果很好(尽管在文章中修复了HostnameVerifier)。
然后,我尝试按照ipolevoy在另一个问题中建议的那样,将“ -DUseSunHttpHandler = true”添加到Weblogic参数中。它开始工作了。
话虽这么说,在Oracle Service Bus服务器上切换HTTP处理程序似乎有点冒险。可能有副作用会在几周后再次咬我。
我还尝试定义自己的trustStore并将其指向包含所需密钥的jssecacert。Weblogic也忽略了它,因为它对每个服务器都有自己的trustStore设置。因此,我求助于管理员手动导入所需的密钥或将Weblogic指向我自己的商店。