SSL证书链不完整(或不被客户端信任)问题,填坑

Posted 零度anngle

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SSL证书链不完整(或不被客户端信任)问题,填坑相关的知识,希望对你有一定的参考价值。

一、SSL证书链信任过程:

浏览器的安装包里,保存着一些它信任的根证书(公钥)。

证书发行商们为了安全,通常会将这些根证书对应的私钥保存在绝对断网的金库里。在金库里用这些根私钥,签发一些“中级”证书,这些中级证书的私钥拥有签发再下一级证书的权限。这些中级私钥被安装到在线服务器上,通过签发网站证书来赚钱。一旦这些服务器被黑,发行商就可以利用金库里物理隔离的根证书私钥,可以签发吊销令,消灭这些中级证书的信任,而不必让各家浏览器彻底不信任这家发行商的根证书。再签一条新的中级发行证书,又是一条能赚钱的好汉。

那么,问题来了?

浏览器只认根证书。中级证书的认证,你(网站)得自己开证明。

一个正确配置的HTTPS网站应该在证书中包含完整的证书链。

比如以 openssl s_client -connect www.wosign.com:443 命令来查看wosign自己的网站配置。

其余内容可以无视,只看Certificate chain这一段:

---

Certificate chain

 0 s:/1.3.6.1.4.1.311.60.2.1.3=CN/1.3.6.1.4.1.311.60.2.1.2=Guangdong/1.3.6.1.4.1.311.60.2.1.1=Shenzhen/businessCategory=Private Organization/serialNumber=440301103308619/C=CN/ST=\\xE5\\xB9\\xBF\\xE4\\xB8\\x9C\\xE7\\x9C\\x81/L=\\xE6\\xB7\\xB1\\xE5\\x9C\\xB3\\xE5\\xB8\\x82/postalCode=518067/street=\\xE6\\xB7\\xB1\\xE5\\x9C\\xB3\\xE5\\xB8\\x82\\xE5\\x8D\\x97\\xE5\\xB1\\xB1\\xE5\\x8C\\xBA\\xE5\\x8D\\x97\\xE6\\xB5\\xB7\\xE5\\xA4\\xA7\\xE9\\x81\\x931057\\xE5\\x8F\\xB7\\xE7\\xA7\\x91\\xE6\\x8A\\x80\\xE5\\xA4\\xA7\\xE5\\x8E\\xA6\\xE4\\xBA\\x8C\\xE6\\x9C\\x9FA\\xE6\\xA0\\x8B502#/O=WoSign\\xE6\\xB2\\x83\\xE9\\x80\\x9A\\xE7\\x94\\xB5\\xE5\\xAD\\x90\\xE8\\xAE\\xA4\\xE8\\xAF\\x81\\xE6\\x9C\\x8D\\xE5\\x8A\\xA1\\xE6\\x9C\\x89\\xE9\\x99\\x90\\xE5\\x85\\xAC\\xE5\\x8F\\xB8/CN=www.wosign.com

   i:/C=CN/O=WoSign CA Limited/CN=WoSign Class 4 EV Server CA

 1 s:/C=CN/O=WoSign CA Limited/CN=WoSign Class 4 EV Server CA

   i:/C=CN/O=WoSign CA Limited/CN=Certification Authority of WoSign

 2 s:/C=CN/O=WoSign CA Limited/CN=Certification Authority of WoSign

   i:/C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing/CN=StartCom Certification Authority

---

其中0、1、2是证书链中每一级证书的序号。0是要被验证的网站所用的证书。其CN应该对应网站域名。

每一个序号后面,s开头的一行是指证书,i开头的一行是指此证书由谁签发。

可以看出,0的CN包含一个疑似中文域名,加一个英文域名www.wosign.com。它的签发者是WoSign CA Limited/CN=WoSign Class 4 EV Server CA。

1的证书就是0的签发者。而1自己又是由另一个证书Certification Authority of WoSign签发的。

再看下一级,2。它说,Certification Authority of WoSign是由StartCom签发的(哈哈,原来是转包商!)

所以这么一级级看下来,浏览器说,哦,2的签发者我认识啊,安装包里有提到,StartCom嘛。签名正确、验证无误,所以信任2。那么也应该信任2签发的1、1签发的0。所以这个网站可以信任。

--

然而,如果网站配置时,在crt文件中只包含了自己,而没包含一个完整到可以被浏览器内置数据验证的证书链,就有可能被浏览器拒绝。比如

 openssl s_client -connect touko.moe:443 

---

Certificate chain

 0 s:/CN=touko.moe

   i:/C=CN/O=WoSign CA Limited/CN=WoSign CA Free SSL Certificate G2

---

只有0一组。说明s行中的touko.moe由i行中的WoSign CA Free SSL Certificate G2签发。没了。

 

这就是此坑最神奇之处:浏览器此时是否验证失败,是不一定的。有2种情况:

A、浏览器自安装以来,从未见过这个i。那么验证会失败。

B、浏览器以前见过、并且验证过i,那么验证会成功。

通常管理员自己会去证书发行商的https网站买证书,浏览器就会验证,然后将验证成功的中间证书全都缓存下来,为以后节省时间。当管理员(错误地)配置完自己的网站,去浏览测试的时候,完全不会遇到问题。因为他的浏览器已经认识这个中间证书了。

但很多用户可能都没访问过其他正确配置的、由这家中级证书签发的网站。所以,验证会因为找不到能够信任的签发者而失败。

简直堪比大众柴油车的尾气排放控制。检查时一切正常。一到外面就放毒。

EDIT:怎么修……大概就是配置服务端的时候加上SSLCertificateChainFile的设置,用证书发行商网站提供的什么什么bundle文件(文件里包含一堆中间证书,用来建立你的证书与某个高信任证书之间的联系)

二、客户端可以通过下面方法解决:

     一般报错如下:

  1. [ERROR] http-8080-Processor25 2010-01-20 15:29:28,640 org.jasig.cas.client.validation.Cas20ServiceTicketValidator     - 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  
  2. 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  
  3.     at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Unknown Source)  
  4.     at com.sun.net.ssl.internal.ssl.SSLSocketImpl.fatal(Unknown Source)  
  5.     at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Unknown Source)  
  6.     at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Unknown Source)  
  7.     at com.sun.net.ssl.internal.ssl.ClientHandshaker.serverCertificate(Unknown Source)  
  8.     at com.sun.net.ssl.internal.ssl.ClientHandshaker.processMessage(Unknown Source)  
  9.     at com.sun.net.ssl.internal.ssl.Handshaker.processLoop(Unknown Source)  
  10.     at com.sun.net.ssl.internal.ssl.Handshaker.process_record(Unknown Source)  
  11.     at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(Unknown Source)  
  12.     at com.sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake(Unknown Source)  
  13.     at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(Unknown Source)  
  14.     at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(Unknown Source)  
  15.     at sun.net.www.protocol.https.HttpsClient.afterConnect(Unknown Source)  
  16.     at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(Unknown Source)  
  17.     at sun.net.www.protocol.http.HttpURLConnection.getInputStream(Unknown Source)  
  18.     at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(Unknown Source)  
  19.     at org.jasig.cas.client.validation.AbstractCasProtocolUrlBasedTicketValidator.retrieveResponseFromServer(AbstractCasProtocolUrlBasedTicketValidator.java:58)  
  20.     at org.jasig.cas.client.validation.AbstractUrlBasedTicketValidator.validate(AbstractUrlBasedTicketValidator.java:167)  
  21.     at org.jasig.cas.client.validation.AbstractTicketValidationFilter.doFilter(AbstractTicketValidationFilter.java:141)  
  22.     at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:137)  
  23.     at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:215)  
  24.     at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:188)  
  25.     at org.jasig.cas.client.authentication.AuthenticationFilter.doFilter(AuthenticationFilter.java:149)  
  26.     at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:215)  
  27.     at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:188)  
  28.     at org.jasig.cas.client.session.SingleSignOutFilter.doFilter(SingleSignOutFilter.java:78)  
  29.     at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:215)  
  30.     at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:188)  
  31.     at com.web114.web.filter.UTF8EncoderFilter.doFilter(UTF8EncoderFilter.java:57)  
  32.     at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:215)  
  33.     at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:188)  
  34.     at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:213)  
  35.     at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:174)  
  36.     at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)  
  37.     at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:117)  
  38.     at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:108)  
  39.     at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:174)  
  40.     at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:874)  
  41.     at org.apache.coyote.http11.Http11BaseProtocol$Http11ConnectionHandler.processConnection(Http11BaseProtocol.java:665)  

       后来上网查了很久,说是证书出问题了,服务器不信任我们自己创建的证书,所以在代码中必须要忽略证书信任问题。只要在创建connection之前调用两个方法:

         trustAllHttpsCertificates();  

         HttpsURLConnection.setDefaultHostnameVerifier(hv); 

具体代码参见:

       

HostnameVerifier hv = new HostnameVerifier()
        public boolean verify(String urlHostName, SSLSession session)
            System.out.println("Warning: URL Host: " + urlHostName + " vs. "
                               + session.getPeerHost());
            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;
        
    
 

 

以上是关于SSL证书链不完整(或不被客户端信任)问题,填坑的主要内容,如果未能解决你的问题,请参考以下文章

怎样的ssl证书才能被信任?

分析一下有哪些原因会导致SSL证书不被信任

解决Charles抓包ssl证书信任问题

iOS 10.3下解决Charles抓包ssl证书信任问题

服务器证书不受信任怎么解决

自己生成的ssl证书与购买的ssl证书有啥区别?