• 服务器证书校验主要针对 WebView 的安全问题。
  • 在 app 中需要通过 WebView 访问 url,因为服务器采用的自签名证书,而不是 ca 认证,使用 WebView 加载 url 的时候会显示为空白,出现无法加载网页的情况。
  • 使用 ca 认证的证书,在 WebView 则可以直接显示出来,不需要特殊处理。
  • 以往针对自签名证书的解决方案是继承 WebViewClient 重写 onReceivedSslError 方法,然后直接使用 handler.proceed(),该方案其实是忽略了证书,存在安全隐患。
  • 安全的方案是当出现了证书问题的时候,读取 asserts 中保存的的根证书,然后与服务器校验,假如通过了,继续执行 handler.proceed(),否则执行 handler.cancel()。

WebViewClient 源码

  • 当证书出现问题的时候,有 2 种情况。系统默认不加载该网页

    • handler.cancel()

    • handler.proceed()

        public class WebViewClient {
      
        	public void onReceivedSslError(WebView view, SslErrorHandler handler,
      SslError error) {
      handler.cancel();
      }
      ... }

以往不安全的解决方案

  • 当出现 ssl error 的时候,直接忽略,依旧打开网页。

  • 继承系统的 WebViewClient , 重写 onReceiverSslError() ,改为 handler.process()

      public class UnSafeWebViewClient extends WebViewClient
    {
    @Override
    public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
    handler.proceed();
    }
    }

安全的解决方案

  • 把服务器的证书放置在 assert 文件夹,当出现 ssl error 的时候进行读取,然后与服务器校验,校验通过了就加载该网页。校验不通过,不打开网页,进行安全提醒。

      public class WebviewClient3 extends WebViewClient {
    private Context context; public WebviewClient3(Context context) {
    this.context = context;
    } @Override
    public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
    test12306(handler, view.getUrl());
    } // 以 12306 的证书为例,因为 12306 的证书是自签名的
    private void test12306(final SslErrorHandler handler, String url) {
    OkHttpClient.Builder builder;
    try {
    builder = setCertificates(new OkHttpClient.Builder(), context.getAssets().open(MainActivity.cer_protal_root));
    } catch (IOException e) {
    builder = new OkHttpClient.Builder();
    }
    Request request = new Request.Builder().url(url)
    .build();
    builder.build().newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
    Log.e("12306 error", e.getMessage());
    handler.cancel();
    } @Override
    public void onResponse(Call call, Response response) throws IOException {
    Log.e("12306 ", response.body().string());
    handler.proceed();
    }
    });
    } private OkHttpClient.Builder setCertificates(OkHttpClient.Builder client, InputStream... certificates) {
    try {
    CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
    KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC");
    keyStore.load(null);
    int index = 0;
    for (InputStream certificate : certificates) {
    String certificateAlias = Integer.toString(index++);
    keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate)); try {
    if (certificate != null)
    certificate.close();
    } catch (IOException e) {
    }
    }
    SSLContext sslContext = SSLContext.getInstance("TLS");
    TrustManagerFactory trustManagerFactory =
    TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    trustManagerFactory.init(keyStore);
    sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());
    SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
    X509TrustManager trustManager = Platform.get().trustManager(sslSocketFactory);
    client.sslSocketFactory(sslSocketFactory, trustManager);
    } catch (Exception e) {
    e.printStackTrace();
    }
    return client;
    }

  • 以上代码可以针对规范的自签名证书进行校验了。但是呢,我们的证书不规范,会出现 Hostname xxx not verified 的情况。这种情况需要对 Hostname 进行校验。需要在 client 上添加如下代码

          client.hostnameVerifier(new HostnameVerifier() {
    @Override
    public boolean verify(String hostname, SSLSession session) {
    String peerHost = session.getPeerHost();//服务器返回的域名
    try {
    X509Certificate[] peerCertificates = (X509Certificate[]) session.getPeerCertificates();
    for (X509Certificate c : peerCertificates) {
    X500Principal subjectX500Principal = c.getSubjectX500Principal();
    String name = new X500p(subjectX500Principal).getName();
    String[] split = name.split(",");
    for (String s : split) {
    if (s.startsWith("CN")) {
    if (s.contains(hostname) && s.contains(peerHost)) {
    return true;
    }
    }
    }
    }
    } catch (SSLPeerUnverifiedException e) {
    e.printStackTrace();
    }
    return false;
    }
    });

获取证书两种方法

  1. 服务器组直接给。如测试 12306 网站的时候,进入网页,12306 会提供根证书的下载
  2. 通过网页获取。
    1. Chrome 浏览器,按 F12 选择 Security
    2. 选择 Certificate Error 的 View certificate
    3. 在弹出的证书,选择详细信息
    4. 在详细信息页面,点击复制到文件,一路下一步,然后选择保存证书的地方,就把证书成功导出来了。

证书的读取

  • 从 assert 中读取文件

      InputStream is = getAssets().open("root.cer");

生成 jks 、 cer 证书的 keytool 命令

  • 生成 jks

      keytool -genkey -alias li_server -keyalg RSA -keystore li_server.jks -validity 3600 -storepass 123456
  • 使用 jks 生成 cer

      keytool -export -alias li_server  -file li_server.cer  -keystore li_server.jks -storepass 123456

基础知识

  • java 平台默认识别 jks 格式的证书文件, 但是 Android 平台只识别 bks 格式的证书文件

注意:

  • 使用本地服务器测试的时候,使用的 IP 地址,如:192.168.2.22,生成的服务器证书需要添加(-ext san=ip:192.168.2.22),否则会出现 Hostname xxx not verified 的问题:

    keytool -genkey -alias li_server -keyalg RSA -keystore li_server.jks -validity 3600 -storepass 123456 -ext san=ip:192.168.2.22

Tomcat 搭建 SSL 环境

  1. 百度搜索 tomcat 本地搭建

  2. 生成的证书 jks 放到 tomcat 的根目录,如:D:\apache-tomcat

  3. 修改 server.xml 文件,在 Service 节点,添加如下代码

     <Connector SSLEnabled="true" acceptCount="100" clientAuth="false"
    disableUploadTimeout="true" enableLookups="true" keystoreFile="li_server.jks" keystorePass="123456" maxSpareThreads="75"
    maxThreads="200" minSpareThreads="5" port="8443"
    protocol="org.apache.coyote.http11.Http11NioProtocol" scheme="https"
    secure="true" sslProtocol="TLS"/>
  4. 重启 tomcat ,然后就可以访问 https 的地址了,端口为 8443,如:https://192.168.123.131:8443/

  5. 通过 chrome 可以看到,该网页不安全提醒。

常见问题

  1. 证书有问题,证书来自不信任的来源

    java.security.cert.CertPathValidatorException: Trust anchor for certification path not found
  2. 配置服务器所使用的证书不具有与尝试连接的服务器匹配的主题或主题备用名称字段

    Hostname xxx not verified:

参考:

Android 安全之 Https 中间人攻击漏洞:http://yaq.qq.com/blog/13

Android HostName 强验证:http://www.cnblogs.com/fengchuxiaodai/p/5962760.html

Android WebView 手动校验 https 证书: http://blog.csdn.net/lsyz0021/article/details/54669914

Android HTTPS : http://blog.csdn.net/lmj623565791/article/details/48129405

Android HostName XXX not verified : https://developer.android.com/training/articles/security-ssl.html#CommonHostnameProbs

SSLPeerUnverifiedException:HostName not verified: https://stackoverflow.com/questions/30745342/javax-net-ssl-sslpeerunverifiedexception-hostname-not-verified

jks 转 bks :http://blog.csdn.net/bigboysunshine/article/details/54134382

jks 转 bks : http://www.cnblogs.com/darkdog/p/4281555.html

Retrofit 使用 HTTPS: http://blog.csdn.net/dd864140130/article/details/52625666

解析证书乱码问题:http://blog.csdn.net/suntongo/article/details/38864413

Android Webview SSL 自签名安全校验解决方案的更多相关文章

  1. Android WebView的Js对象注入漏洞解决方案

    http://blog.csdn.net/leehong2005/article/details/11808557/ webview调用以下文件,就可以打印sdcard 文件名 <!DOCTYP ...

  2. Android WebView 详解

    相关API 相关类介绍 WebResourceRequest 添加于API21,封装了一个Web资源的请求信息,包含:请求地址,请求方法,请求头,是否主框架,是否用户点击,是否重定向 WebResou ...

  3. Android WebView常见问题及解决方案汇总

    Android WebView常见问题解决方案汇总: 就目前而言,如何应对版本的频繁更新呢,又如何灵活多变地展示我们的界面呢,这又涉及到了web app与native app之间孰优孰劣的争论. 于是 ...

  4. Android WebView访问SSL证书网页(onReceivedSslError)

    Android WebView访问https SSL证书网页,如淘宝,需要在onReceivedSslError添加SSL支持 webview.setWebViewClient(new WebView ...

  5. Android WebView常见问题解决方案汇总

    问题目录: 1.为WebView自定义错误显示界面: 2.WebView cookies清理 3.清理cache 和历史记录 4.判断WebView是否已经滚动到页面底端 5.URL拦截 6.处理We ...

  6. Android之Android WebView常见问题及解决方案汇总

    如有转载,请声明出处: 时之沙: http://blog.csdn.net/t12x3456 Android WebView常见问题解决方案汇总: 就目前而言,如何应对版本的频繁更新呢,又如何灵活多变 ...

  7. Android中破解应用签名校验的后续问题处理方案(闪退和重启现象以及无效问题)

    一.前言 之前已经写了一个爆破签名校验的工具kstools,很多同学也在使用,但是也反馈了不少问题,之前一篇文章也介绍了,关于爆破之后第三方登录问题修复,这篇我们在综合说明一下一些后遗症问题,关于ks ...

  8. webview之如何设计一个优雅健壮的Android WebView?(下)(转)

    转载:https://iluhcm.com/2018/02/27/design-an-elegant-and-powerful-android-webview-part-two/ (这篇文章写得有点晚 ...

  9. 如何设计一个优雅健壮的Android WebView?(下)

    转:如何设计一个优雅健壮的Android WebView?(下) 前言 在上文<如何设计一个优雅健壮的Android WebView?(上)>中,笔者分析了国内WebView的现状,以及在 ...

随机推荐

  1. Sql server中依据存储过程中的部分信息查找存储过程名称的方法【视图和Function】

    1.查询的语句: select a.id,b.name,a.*,b.* from syscomments a join sysobjects b on a.id=b.id where b.xtype= ...

  2. JavaWeb开发之普通图片验证码生成技术与算术表达式验证码生成技术

    转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6134649.html    另:算术验证码生成的JSP.Servlet实现均已移植github:https:/ ...

  3. 〖Android〗CyanogenMod同步错误的解决

    1. 错误信息: repo sync CyanogenMod/Superuser Fetching project CyanogenMod/Superuser Fetching projects: % ...

  4. jenkins关闭和重启

    我们用jar -jar jenkins.war来启动jenkins服务器,那么我们如何关闭或者重启jenkins服务器呢?经过搜索找到了相应的方法. 关闭jenkins服务 只需要在访问jenkins ...

  5. Window磁盘错误修复chkdsk

    场景: 狗日的垃圾移动磁盘.U盘质量太差劲,会出现磁盘坏道.读写异常~心疼数据.... 命令: 01.Win+R 调出cmd 02.根据移动盘符或磁盘盘符,进行修复 03.执行修复chkdsk /f  ...

  6. Ubuntu18.04中配置QT5.11开发环境

    准备工作 参考 https://wiki.qt.io/Install_Qt_5_on_Ubuntu . # 安装g++ sudo apt install build-essential # sudo ...

  7. iOS UISlider滑动块触摸范围调整变大

    正常情况下,我们自定义的滑动区域都不会太大,否则UI不美观,但是这样,又会手势不灵敏,用户体验变差. 如何解决? 这里有一种方案:封装一个继承UISlider的自定义类,重写thumbRectForB ...

  8. linux vi 删除一行,复制一行命令,删除所有空白行

    删除所有空白行(^是行的开始,\s*是零个或者多个空白字符:$是行尾) :g/^\s*$/d 删除一行: dd 复制一行: yy ,之后是要 p 才会贴上来的.

  9. Jetty使用内存过大的解决方案

    之前用Jetty做过一个消息通知服务器,主要功能就是其他各个子系统如果有需要push给客户端消息的就把这个消息发给我的Server,我用WebSocket来推送给客户端~ 程序上线一段时间之后运维工程 ...

  10. NFS安装及优化过程--centos6.6

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 3 ...