Https证书验证相关;security.cert.CertPathValidatorException: Trust anchor for certification path not found.

Https 中用到的证书术语,大概关系

CA机构:颁发证书的机构,你的域名想上Https,需要在CA机构注册,申请证书,如百度https://www.baidu.com/的证书
服务器证书:如https://www.baidu.com/,对应一家公司,去CA申请证书后,会有一个证书(服务器程序会用到),下文中将此类证书称为服务器证书
根证书:CA机构用来加解密,验证你申请证书的证书,每个平台会内置这些CA机构的证书(也叫根证书)如 Android,程序与https通信的时候会用到根证书验证Https URL对应的证书是否合法

查看一个域名的服务器证书和CA根证书

如:https://www.baidu.com/域名
在谷歌浏览器中打开https://www.baidu.com/可以看到(如下图),点击安全图标出现如下弹框
Https证书验证相关;security.cert.CertPathValidatorException: Trust anchor for certification path not found._第1张图片
点击证书可以查看证书相关内容:颁发者,有效期等
Https证书验证相关;security.cert.CertPathValidatorException: Trust anchor for certification path not found._第2张图片
导出服务器证书 : 点击详细信息->复制到文件->下一步->选择格式DER ->下一步,直到导出成功 最后会有一个**.cer**的证书文件
导出CA根证书 :点击根证书路径->点击最上面的根证书->剩下步骤就和导出服务器证书一样
Https证书验证相关;security.cert.CertPathValidatorException: Trust anchor for certification path not found._第3张图片

window上证书查看

window+R 下输入certmgr.msc,确定
Https证书验证相关;security.cert.CertPathValidatorException: Trust anchor for certification path not found._第4张图片
出现证书列表:选择某个证书,双击出现证书界面,导出就和上面的操作一样
Https证书验证相关;security.cert.CertPathValidatorException: Trust anchor for certification path not found._第5张图片

查看Android 设备上的证书,安装证书到Android设备

查看设备上的证书

adb shell
cd system/etc/security/cacerts/
#列出所有证书,如下图 ,一堆57692373.0文件
ls 
#查找某个机构颁发的证书 如 cfca
grep -inr "cfca" ./
#查看某个证书文件
cat 9282e51c.0

列出所有证书
在这里插入图片描述
查找某个机构颁发的证书 如 cfca,查看cfca证书文件
Https证书验证相关;security.cert.CertPathValidatorException: Trust anchor for certification path not found._第6张图片
安装证书

  1. 把之前的.cer根证书文件push到android设备上
adb push "C:\Users\my\Desktop\CFCA EV ROOT.cer" /sdcard/baidu
  1. 在设置中安装证书
    进入设置->安全->从手机存储安装(从手机存储安装证书),不同设备可能位置不一样;选择刚才push的证书文件
    Https证书验证相关;security.cert.CertPathValidatorException: Trust anchor for certification path not found._第7张图片
    note:如果是ODM公司,rom开发可以把根证书内置到系统中

java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.

上面的错误是在一个Android 5.0的设备的Https请求中的,其他设备没问题;通过上面的步骤安装完证书就好了,然而这种方法只能验证问题,不能当做解决问题的方案。下面提供两个解决思路

  1. 服务器提供的https接口,注册的CA机构根证书不是很通用,在某些设备上没有,那就需要服务器更换证书,换一个更通用的CA机构,思路没毛病,服务器的问题。
  2. 终端处理:
  • 忽略https根证书验证,设置信任所有服务器证书(有点违背使用https的原则,不安全)
  • 终端内置服务器证书,修改证书验证逻辑,与内置的服务器证书匹配(证书公钥,颁发机构,颁发给谁等信息)就算验证通过
  • 终端内置根证书,代码安装根证书

OkHttp的https证书验证逻辑
使用OkHttp默认证书验证逻辑:不设置sslSocketFactory和hostnameVerifier,默认使用系统的根证书验证服务器证书,如果对应的根证书不存在,报错Trust anchor for certification path not found.;这种就适合使用CA的证书(也有可能CA机构的根证书不够通用而没有内置),不适合像12306这种自己颁发的证书
自定义自己的证书验证逻辑:直接让所有的服务器证书通过不验证;比较服务器证书公钥,颁发机构,颁发给谁等信息验证某个特定的服务器证书

相关代码
OkHttp默认证书验证逻辑
Https证书验证相关;security.cert.CertPathValidatorException: Trust anchor for certification path not found._第8张图片

  private X509TrustManager systemDefaultTrustManager() {
    try {
      TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
          TrustManagerFactory.getDefaultAlgorithm());
      trustManagerFactory.init((KeyStore) null);
      TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
      if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
        throw new IllegalStateException("Unexpected default trust managers:"
            + Arrays.toString(trustManagers));
      }
      return (X509TrustManager) trustManagers[0];
    } catch (GeneralSecurityException e) {
      throw assertionError("No System TLS", e); // The system has no TLS. Just give up.
    }
  }

  private SSLSocketFactory systemDefaultSslSocketFactory(X509TrustManager trustManager) {
    try {
      SSLContext sslContext = SSLContext.getInstance("TLS");
      sslContext.init(null, new TrustManager[] { trustManager }, null);
      return sslContext.getSocketFactory();
    } catch (GeneralSecurityException e) {
      throw assertionError("No System TLS", e); // The system has no TLS. Just give up.
    }
  }

大体意思就是使用系统的CA根证书验证服务器证书

忽略https根证书验证,设置信任所有服务器证书

X509TrustManager接口就是用来验证证书的,实现一个空的接口,就是不验证证书合法性;验证服务器证书不合格就抛出异常

通过:OkHttpClient的sslSocketFactory和hostnameVerifier方法设置;测试有效

封装的工具类:

public class HttpsCertificateVerifyHelper {

    /*------------------------设置信任所有证书 start------------------------*/
    public static OkHttpClient.Builder trustAllCertificate(OkHttpClient.Builder okBuilder) {
        initSSLSocketFactoryAndX509TrustManager();
        //sslSocketFactory 方法: 设置信任所有证书,无需使用CA证书对服务器证书验证
        //hostnameVerifier方法:设置域名验证成功,我没有设置这个,https接口也是可以使用的,可能默认返回的true;
        // 但是我让其返回false,接口出错Hostname  xxxxhost not verified
        okBuilder.sslSocketFactory(sslSocketFactory, trustManager)
                .hostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        return true;
                    }
                });
        return okBuilder;

    }

    private static X509TrustManager trustManager;
    private static SSLSocketFactory sslSocketFactory;

    private static void initSSLSocketFactoryAndX509TrustManager() {
        try {
            trustManager = new X509TrustManager() {

                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {

                }

                @Override
                public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {

                }

                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }
            };
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, new TrustManager[]{trustManager}, new SecureRandom());
            sslSocketFactory = sslContext.getSocketFactory();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /*------------------------设置信任所有证书 end------------------------*/


    /*------------------------设置信任内置的服务器证书 start------------------------*/

    /**
     * 指定本地的服务器证书来认证 服务器的证书(服务器证书)
     *
     * @param okBuilder
     * @param context
     * @param assetsFileName 证书在assets中的文件名  .cer 格式或者 .pem格式
     * @return
     */
    public static OkHttpClient.Builder trustSpecificCertificate(OkHttpClient.Builder okBuilder, Context context, String assetsFileName) {
        initSSLSocketFactoryAndX509TrustManager(context, assetsFileName);
        //sslSocketFactory 方法: 设置信任所有证书,无需使用CA证书对服务器证书验证
        //hostnameVerifier方法:设置域名验证成功,我没有设置这个,https接口也是可以使用的,可能默认返回的true;
        // 但是我让其返回false,接口出错Hostname  xxxxhost not verified
        okBuilder.sslSocketFactory(sslSocketFactory, trustManager)
                .hostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        return true;
                    }
                });
        return okBuilder;

    }

    public static void initSSLSocketFactoryAndX509TrustManager(Context context, String assetsFileName) {
        try {
            trustManager = new X509TrustManager() {

                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {

                }

                @Override
                public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                    if (chain == null || chain.length == 0) {
                        throw new CertificateException("checkServerTrusted: X509Certificate array is null");
                    }

                    if (!(null != authType && authType.equals("ECDHE_RSA"))) {
                        throw new CertificateException("checkServerTrusted: AuthType is not ECDHE_RSA");
                    }
                    //判断证书是否是本地信任列表里颁发的证书(系统默认的验证)
                    try {
                        TrustManagerFactory factory = TrustManagerFactory.getInstance("X509");
                        factory.init((KeyStore) null);
                        for (TrustManager trustManager : factory.getTrustManagers()) {
                            ((X509TrustManager) trustManager).checkServerTrusted(chain, authType);
                        }
                        return;//用系统的证书验证服务器证书,验证通过就不需要继续验证证书信息;也可以注释掉,继续走自己的服务器证书逻辑  
                    } catch (Exception e) {
                        e.printStackTrace();
                        //注意这个地方不能抛异常,用系统的证书验证服务器证书,没通过就用自己的验证规则
//                        throw new CertificateException(e);
                    }

                    //获取本地证书中的信息
                    String clientEncoded = "";//公钥
                    String clientSubject = "";//颁发给
                    String clientIssUser = "";//颁发机构
                    try (InputStream inputStream = getAssetFileInputStream(context, assetsFileName)) {
                        CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
                        X509Certificate clientCertificate = (X509Certificate) certificateFactory.generateCertificate(inputStream);
                        clientEncoded = new BigInteger(1, clientCertificate.getPublicKey().getEncoded()).toString(16);
                        clientSubject = clientCertificate.getSubjectDN().getName();
                        clientIssUser = clientCertificate.getIssuerDN().getName();

                    } catch (Exception e) {
                        e.printStackTrace();
                        throw new CertificateException(e);
                    }

                    //获取网络中的证书信息
                    X509Certificate certificate = chain[0];
                    PublicKey publicKey = certificate.getPublicKey();
                    String serverEncoded = new BigInteger(1, publicKey.getEncoded()).toString(16);

                    if (!clientEncoded.equals(serverEncoded)) {
                        throw new CertificateException("server's PublicKey is not equals to client's PublicKey");
                    }
                    String subject = certificate.getSubjectDN().getName();
                    if (!clientSubject.equals(subject)) {
                        throw new CertificateException("server's SubjectDN is not equals to client's SubjectDN");
                    }
                    String issuser = certificate.getIssuerDN().getName();
                    if (!clientIssUser.equals(issuser)) {
                        throw new CertificateException("server's IssuerDN is not equals to client's IssuerDN");
                    }
                }

                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }
            };
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, new TrustManager[]{trustManager}, new SecureRandom());
            sslSocketFactory = sslContext.getSocketFactory();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    private static InputStream getAssetFileInputStream(Context context, String assetsFileName) {
        try {
            return context.getAssets().open(assetsFileName);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /*------------------------设置信任内置的服务器证书 end------------------------*/
}

使用工具类设置OkHttpClient

         HttpsCertificateVerifyHelper.trustAllCertificate(new OkHttpClient.Builder()
                //.cache(cache)
                .eventListenerFactory(HttpEventListener.FACTORY)
                .addInterceptor(cacheControlInterceptor)
                // .addNetworkInterceptor(cacheControlInterceptor)
                .addInterceptor(logInterceptor)
                .retryOnConnectionFailure(false)
                .connectTimeout(getConnectTimeout(), TimeUnit.SECONDS)
                .readTimeout(getReadTimeout(), TimeUnit.SECONDS))
                .build();

参考:https://www.jianshu.com/p/8a16ac7ee444

终端内置服务器证书
my.cer放在main/assets目录下
大概的验证逻辑:先使用默认的本地根证书验证服务器证书,看自己项目情况可以去掉(代码 return可以注释掉);再使用自己的服务器证书验证逻辑(比较证书的公钥,颁发者,颁发给信息);测试有效
使用工具类设置OkHttpClient

          HttpsCertificateVerifyHelper.trustSpecificCertificate(new OkHttpClient.Builder()
                //.cache(cache)
                .eventListenerFactory(HttpEventListener.FACTORY)
                .addInterceptor(cacheControlInterceptor)
                // .addNetworkInterceptor(cacheControlInterceptor)
                .addInterceptor(logInterceptor)
                .retryOnConnectionFailure(false)
                .connectTimeout(getConnectTimeout(), TimeUnit.SECONDS)
                .readTimeout(getReadTimeout(), TimeUnit.SECONDS), ContextUtil.getInstance().getContext(),"my.cer")
                .build();

参考:https://blog.csdn.net/masonblog/article/details/77712047

终端内置根证书,代码安装根证书

下面是安装证书的代码,没有验证过;有需要的可以参考

    /**
     * 安裝指定的CA根证书证书
     *
     * @param context
     * @param assetsFileName
     */
    public static void installSpecificCertificate(Context context, String assetsFileName) {
        Intent intent = KeyChain.createInstallIntent();
        try (InputStream inputStream = getAssetFileInputStream(context, assetsFileName)) {
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            X509Certificate clientCertificate = (X509Certificate) certificateFactory.generateCertificate(inputStream);
            //将证书传给系统
            intent.putExtra(KeyChain.EXTRA_CERTIFICATE, clientCertificate.getEncoded());
            //此处为给证书设置默认别名,第二个参数可自定义,设置后无需用户输入
            intent.putExtra("name", "别名(可设置默认)");
            context.startActivity(intent);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

参考:https://blog.csdn.net/wwp9527/article/details/82757850

你可能感兴趣的:(Android)