zoukankan      html  css  js  c++  java
  • 记一次因证书问题导致请求失败问题SSLHandshakeException

    记一次因证书问题导致请求失败问题SSLHandshakeException

    转载请注明出处:https://www.cnblogs.com/funnyzpc/p/10989813.html

    最近接一外部接口,接口在本地开发调试及测试都无任何问题(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
  • 相关阅读:
    转载一篇文章 python程序员经常犯的10个错误
    外部表与partition
    grpc 入门(二)-- 服务接口类型
    用例图简介(转)
    UML类图(Class Diagram)中类与类之间的关系及表示方式(转)
    快速搭建fabric-v1.1.0的chaincode开发环境
    ubuntu networking 与 network-manager
    [转]bashrc与profile区别
    超矩链--基于矩阵的分布式账本
    adb 在windows7中的使用
  • 原文地址:https://www.cnblogs.com/funnyzpc/p/10989813.html
Copyright © 2011-2022 走看看