记一次因证书问题导致请求失败问题SSLHandshakeException
记一次因证书问题导致请求失败问题sslhandshakeexception
转载请注明出处:
最近接一外部接口,接口在本地开发调试及测试都无任何问题(windows下),而上测试环境后测第一次就直接报错误,
错误是这样子的:
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 at sun.security.ssl.alerts.getsslexception(alerts.java:192) at sun.security.ssl.sslsocketimpl.fatal(sslsocketimpl.java:1917) at sun.security.ssl.handshaker.fatalse(handshaker.java:301) at sun.security.ssl.handshaker.fatalse(handshaker.java:295) at sun.security.ssl.clienthandshaker.servercertificate(clienthandshaker.java:1369) at sun.security.ssl.clienthandshaker.processmessage(clienthandshaker.java:156) at sun.security.ssl.handshaker.processloop(handshaker.java:925) at sun.security.ssl.handshaker.process_record(handshaker.java:860) at sun.security.ssl.sslsocketimpl.readrecord(sslsocketimpl.java:1043) at sun.security.ssl.sslsocketimpl.performinitialhandshake(sslsocketimpl.java:1343) at sun.security.ssl.sslsocketimpl.starthandshake(sslsocketimpl.java:1371) at sun.security.ssl.sslsocketimpl.starthandshake(sslsocketimpl.java:1355)
enn~,首先那个接口地址是https的,服务器是linux的;以上错误其大意是无法找到及验证有效证书,再想想:不对啊,本地jdk和服务器的jdk都是oracle官方jdk 1.8呀,照理说
本地调试没问题在服务端应该也不会有什么问题呢~
诶~,不管怎么分析都还是要解决问题呀,首先我分析到这又两个问题点:
- 本地和服务器os不一致
- 接口地址的ssl证书存在不兼容或其他问题
怎么办?要求对方检查证书配置,可能性不大,剩下的就只剩下一种方式:做兼容,就是在请求的时候信任对方的证书。
于是有了第一版。
因为我使用的是closeablehttpclient,做的请求管理,不如在让closeablehttpclient兼容https与http不就好了,寻思一项,搜索一番代码即成
(这里只给出核心代码)
// 之前 // private static closeablehttpclient httpclient = httpclients.custom().build(); // 之后 private static closeablehttpclient httpclient; static { try { system.out.println("===>01"); // 忽略证书 sslcontextbuilder sslbuilder = new sslcontextbuilder().loadtrustmaterial(null, new trustselfsignedstrategy()); //不进行主机名验证 sslconnectionsocketfactory sslconnectionsocketfactory = new sslconnectionsocketfactory(sslbuilder.build(), noophostnameverifier.instance); registry<connectionsocketfactory> registry = registrybuilder.<connectionsocketfactory>create() .register("http", new plainconnectionsocketfactory()) .register("https", sslconnectionsocketfactory) .build(); poolinghttpclientconnectionmanager cm = new poolinghttpclientconnectionmanager(registry); cm.setmaxtotal(100); httpclient = httpclients.custom() .setsslsocketfactory(sslconnectionsocketfactory) .setdefaultcookiestore(new basiccookiestore()) .setconnectionmanager(cm).build(); } catch (exception e) { e.printstacktrace(); system.out.println("===>02"); httpclient = httpclients.custom().build(); } }
bingo ~,上线测 。。。
oh~,no,依然是这个错:
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 at sun.security.ssl.alerts.getsslexception(alerts.java:192) ......
待我分析一番,发现上面的代码仅仅只是为了不验证对方主机,完全没有理会证书的错误。。。欸~,这是个问题。
后我又想起之前上上家公司也有出现过这个问题,哈~,有办法了,找到源码把主要的几句copy过来走走不就好了。
于是,第二版
核心代码:
hostnameverifier hv = new hostnameverifier() { public boolean verify(string urlhostname, sslsession session) { return true; } }; private static void trustallhttpscertificates() throws exception { javax.net.ssl.trustmanager[] trustallcerts = new javax.net.ssl.trustmanager[1]; javax.net.ssl.trustmanager tm = new mitm(); trustallcerts[0] = tm; javax.net.ssl.sslcontext sc = javax.net.ssl.sslcontext .getinstance("ssl"); sc.init(null, trustallcerts, null); javax.net.ssl.httpsurlconnection.setdefaultsslsocketfactory(sc .getsocketfactory()); } static class mitm implements javax.net.ssl.trustmanager, javax.net.ssl.x509trustmanager { public java.security.cert.x509certificate[] getacceptedissuers() { return null; } public boolean isservertrusted( java.security.cert.x509certificate[] certs) { return true; } public boolean isclienttrusted( java.security.cert.x509certificate[] certs) { return true; } public void checkservertrusted( java.security.cert.x509certificate[] certs, string authtype) throws java.security.cert.certificateexception { return; } public void checkclienttrusted( java.security.cert.x509certificate[] certs, string authtype) throws java.security.cert.certificateexception { return; } } // 在访问前调用 trustallhttpscertificates(); httpsurlconnection.setdefaulthostnameverifier(hv);
一整折腾后上线部署测试,啊~,还是同样的错误。。。
分析代码,看到,这种处理逻辑只针对自定义ssl证书有效,对于我现有的情况丁点问题都解决不了
终版
其实业务代码的什么都没改,只是给jdk添加了点儿东西。
主要解决思路是让jdk忽略指定域名的ssl证书。
//installcert.java import java.io.*; import java.net.url; import java.security.*; import java.security.cert.*; import javax.net.ssl.*; public class installcert { public static void main(string[] args) throws exception { string host; int port; char[] passphrase; if ((args.length == 1) || (args.length == 2)) { string[] c = args[0].split(":"); host = c[0]; port = (c.length == 1) ? 443 : integer.parseint(c[1]); string p = (args.length == 1) ? "changeit" : args[1]; passphrase = p.tochararray(); } else { system.out.println("usage: java installcert <host>[:port] [passphrase]"); return; } file file = new file("jssecacerts"); if (file.isfile() == false) { char sep = file.separatorchar; file dir = new file(system.getproperty("java.home") + sep + "lib" + sep + "security"); file = new file(dir, "jssecacerts"); if (file.isfile() == false) { file = new file(dir, "cacerts"); } } system.out.println("loading keystore " + file + "..."); inputstream in = new fileinputstream(file); keystore ks = keystore.getinstance(keystore.getdefaulttype()); ks.load(in, passphrase); in.close(); sslcontext context = sslcontext.getinstance("tls"); trustmanagerfactory tmf = trustmanagerfactory.getinstance(trustmanagerfactory.getdefaultalgorithm()); tmf.init(ks); x509trustmanager defaulttrustmanager = (x509trustmanager)tmf.gettrustmanagers()[0]; savingtrustmanager tm = new savingtrustmanager(defaulttrustmanager); context.init(null, new trustmanager[] {tm}, null); sslsocketfactory factory = context.getsocketfactory(); system.out.println("opening connection to " + host + ":" + port + "..."); sslsocket socket = (sslsocket)factory.createsocket(host, port); socket.setsotimeout(10000); try { system.out.println("starting ssl handshake..."); socket.starthandshake(); socket.close(); system.out.println(); system.out.println("no errors, certificate is already trusted"); } catch (sslexception e) { system.out.println(); e.printstacktrace(system.out); } x509certificate[] chain = tm.chain; if (chain == null) { system.out.println("could not obtain server certificate chain"); return; } bufferedreader reader = new bufferedreader(new inputstreamreader(system.in)); system.out.println(); system.out.println("server sent " + chain.length + " certificate(s):"); system.out.println(); messagedigest sha1 = messagedigest.getinstance("sha1"); messagedigest md5 = messagedigest.getinstance("md5"); for (int i = 0; i < chain.length; i++) { x509certificate cert = chain[i]; system.out.println (" " + (i + 1) + " subject " + cert.getsubjectdn()); system.out.println(" issuer " + cert.getissuerdn()); sha1.update(cert.getencoded()); system.out.println(" sha1 " + tohexstring(sha1.digest())); md5.update(cert.getencoded()); system.out.println(" md5 " + tohexstring(md5.digest())); system.out.println(); } system.out.println("enter certificate to add to trusted keystore or 'q' to quit: [1]"); string line = reader.readline().trim(); int k; try { k = (line.length() == 0) ? 0 : integer.parseint(line) - 1; } catch (numberformatexception e) { system.out.println("keystore not changed"); return; } x509certificate cert = chain[k]; string alias = host + "-" + (k + 1); ks.setcertificateentry(alias, cert); outputstream out = new fileoutputstream("jssecacerts"); ks.store(out, passphrase); out.close(); system.out.println(); system.out.println(cert); system.out.println(); system.out.println ("added certificate to keystore 'jssecacerts' using alias '" + alias + "'"); } private static final char[] hexdigits = "0123456789abcdef".tochararray(); private static string tohexstring(byte[] bytes) { stringbuilder sb = new stringbuilder(bytes.length * 3); for (int b : bytes) { b &= 0xff; sb.append(hexdigits[b >> 4]); sb.append(hexdigits[b & 15]); sb.append(' '); } return sb.tostring(); } private static class savingtrustmanager implements x509trustmanager { private final x509trustmanager tm; private x509certificate[] chain; savingtrustmanager(x509trustmanager tm) { this.tm = tm; } public x509certificate[] getacceptedissuers() { throw new unsupportedoperationexception(); } public void checkclienttrusted(x509certificate[] chain, string authtype) throws certificateexception { throw new unsupportedoperationexception(); } public void checkservertrusted(x509certificate[] chain, string authtype) throws certificateexception { this.chain = chain; tm.checkservertrusted(chain, authtype); } } }
具体解决步骤:
- 编译文件
javac installcert.java
- 添加信任
java installcert 域名地址
- 上传证书(需手动将网站证书导出)
rz => 证书.cer
- 导入证书(密码:changeit)
echo $java_home
keytool -import -alias ll1 -keystore $java_home/jre/lib/security/cacerts -file /home/证书.cer
上一篇: JDK动态代理
下一篇: 【笔记】对自定义异常的理解(Java)