欢迎加入QQ讨论群258996829
麦子学院 头像
苹果6袋
6
麦子学院

Android学习之HTTPS的应用详解

发布时间:2017-09-04 20:21  回复:0  查看:3417   最后回复:2017-09-04 20:21  
现在做 Android开发 ,或多或少都会接触到一些HTTPS 的网络请求,作为新人,踩坑是避免不了的,本文 将忽略HTTP 和 HTTPS 的一些网络基础知识,单纯的从代码角度浅谈 HTTPS 在 Android 中的应用,针对用  自签名证书进行单项验证的HTTPS 请求  ,如果是有CA 或者 RA 签发的证书, Android 底层会进行证书的相关校验,不必自己实现相关的功能。
   一、Android支持HTTPS的核心设计
  要想在Android 上实现 HTTPS 通讯,最核心的有两点,  证书校验  和  域名校验  。其中前者是必须的,后者是非必须的。
   证书校验
  如果要想在Android 上进行 HTTPS 通讯,通常会将证书内置在 APP 内,或者将证书的公钥提取出来,直接以字符串的形式写在类里面。其实这两种方式都差不多,区别就是证书里面不仅仅包含公钥,可能还有一些其他的信息。
  由于是自签名的证书,如果是直接发起HTTPS 请求的话,将会出现以下异常信息:
   javax.net.ssl.SSLHandshakeException:  java.security.cert.CertPathValidatorException:  Trust  anchor  for  certification  path  not found.
  出现这个异常信息的原因就是自签名证书不被系统所信任,如果出现这个错误,说明HTTPS 请求在 TLS 握手过程中被中断了。
  要想实现Android 系统信任服务器自签名的证书,核心思想如下:
   1、通过流将服务器自签名证书转换成X.509格式
  证书放在Assets 目录,其他目录同理获取输入流
   InputStream inputStream = mContext.getAssets().open("jetty_ssl.cer"); CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); Certificate certificate = certificateFactory.generateCertificate(inputStream);
  Certificate 就是获取到的 X.509 格式的证书类。
   2、通过服务器自签名证书转换的X.509证书生成包含服务端证书的KeyStore对象
  获取一个空的KeyStore 对象,所以这里的输入流和密码都为 null 。
  String defaultType = KeyStore.getDefaultType();
  KeyStore keyStore = KeyStore.getInstance(defaultType);
  keyStore.load( null, null);// 输入流和密码都为 null
  将服务器自签名证书生成的X.509 证书添加进 KeyStore
   keyStore.setCertificateEntry("certificate",certificate);
   3、用包含服务器证书的KeyStore生成一个TrustManager
  这种方式生成的TrustManager 里面包含服务器证书强校验机制,也就是说以这种方式生成的 TrustManager 都将使用强校验机制,服务器返回的证书必须匹配。
   String defaultAlgorithm = TrustManagerFactory.getDefaultAlgorithm();// 获取默认的 TrustManagerFactory 算法名称
  TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(defaultAlgorithm);
  trustManagerFactory.init(keyStore);
   4、通过TrustManager获取到一个包含信任服务器自签名证书的SSLContext
  SSLContext sslContext = SSLContext.getInstance("TLS");
  sslContext.init( null,trustManagerFactory.getTrustManagers(), null);
  到这里就已经到了证书校验的关键所在,SSLContext 包含证书校验的关键信息,它可以通过下面方法获取到 HTTPS 请求的关键参数 SSLSocketFactory 。
   SSLSocketFactory socketFactory = sslContext.getSocketFactory();
  如果你是使用OkHttp3.0 网络框架,可以使用 sslSocketFactory() 方法将包含服务器证书校验的 SSLSocketFactory 传入 OkHttp3.0 的配置参数中,在发起 HTTPS 前, OkHttp3.0 框架会去验证证书的合法性。
   OkHttpClient okHttpClient = new OkHttpClient.Builder()
  .sslSocketFactory(socketFactory, (X509TrustManager) trustManagers[0])
  .hostnameVerifier(DO_NOT_VERIFY)
  .build();
  其他网络框架同样有类似SSLSocketFactory 作为参数验证证书合法性的方法,可以自行 Google 相关的资料。
  以上证书校验流程仅仅是本地证书合法性校验,服务器本地证书匹配校验过程是在HTTPS 请求发送后系统(其实是 TLS 协议)完成的,如果证书匹配不成功将自动中断网络请求。正常情况下通过以上的校验已经可以正常的进行 HTTPS 通讯了,下面就开始将一些非正常情况下出现的坑该怎么填。
   域名校验
  通常情况下正确的自签名证书是不必重新域名校验的,需要重新域名校验的情况出现在服务器生成的证书没有包括域名信息,或者干脆服务器没有绑定域名,直接使用的是IP 访问方式。
  SSL 连接有两个关键环节。
  首先是  验证证书是否来自值得信任的来源  ,这个环节自签名证书可以通过自定义TrustManager 来解决,上面通过流的方式获取的 TrustManager 从某种意义上来说就是属于自定义 TrustManager ,在这里多说一句,如果是自定义 TrustManager ,强烈建议开启证书强验证,不要进行弱验证。如果证书弱验证, SSL 握手环节无法成功交换秘钥,数据其实还是明文传输的。以下是自定义 TrustManager 正确开启证书强校验的方式。
   private  class  PreTrustManager  implements  X509TrustManager {
  @Override
   public  void  checkClientTrusted(X509Certificate[] x509Certificates, String authType)  throws CertificateException {
  // 校验客户端证书,用于 HTTPS 双向认证
  }
  @Override
   public  void  checkServerTrusted(X509Certificate[] x509Certificates, String authType)  throws CertificateException {
  // 校验服务端证书,实现强校验,强烈不推荐弱校验
   if (x509Certificates ==  null) {
   throw  new IllegalArgumentException("Check Server X509Certificate[] is null");
  }
   if (x509Certificates.length < 0) {
   throw  new IllegalArgumentException("Check Server X509Certificate[] is empty");
  }
   for (X509Certificate x509Certificate : x509Certificates) {
  // 检测服务端证书签名时候有问题
  x509Certificate.checkValidity();
   try {
  x509Certificate.verify(getX509Certificate().getPublicKey());
  }  catch (NoSuchAlgorithmException e) {
  e.printStackTrace();
  }  catch (InvalidKeyException e) {
  e.printStackTrace();
  }  catch (NoSuchProviderException e) {
  e.printStackTrace();
  }  catch (SignatureException e) {
  e.printStackTrace();
  }
  }
  }
  @Override
   public X509Certificate[] getAcceptedIssuers() {
   return  new X509Certificate[0];
  }
  }
   private X509Certificate getX509Certificate(){
   try {
  InputStream inputStream = mContext.getAssets(). open("jetty_ssl.cer");
  CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
  X509Certificate certificate = (X509Certificate) certificateFactory.generateCertificate(inputStream);
   return certificate;
  }  catch (IOException e) {
  e.printStackTrace();
  }  catch (CertificateException e) {
  e.printStackTrace();
  }
   return null;
  }
  其次  确保正在通信的服务器提供正确的证书  。如果没有提供,通常会看到类似于下面的异常信息:
   javax.net.ssl.SSLPeerUnverifiedException:  Hostname " 你服务器的域名 "  not  verified:
  出现此类问题的原因通常是由于服务器证书中配置的域名和客户端请求的域名不一致所导致的。
  有两种解决方案,一种是服务器用真实的域名重新生成正确的证书,这种解决方式不做讨论;还有一种是客户端自定义HostnameVerifier ,添加域名校验白名单,同样采用域名强校验方式。
   private HostnameVerifier TRUST_HOST_NAME =  new HostnameVerifier() {
  @Override
   public  boolean  verify(String hostname, SSLSession session) {
  //  设置接受的域名集合
   if (hostname.equals(" 服务器域名 "))  {
   return  true;
  }
   return  false;
  }
  };
  以下弱校验方式会导致安全隐患,强烈不推荐。
   private HostnameVerifier TRUST_HOST_NAME =  new HostnameVerifier() {
  @Override
   public  boolean  verify(String hostname, SSLSession session) {
   return  true;
  }
  };
   来源:简书
您还未登录,请先登录

热门帖子

最新帖子