关于Android的https通讯安全
原文链接:http://pingguohe.net/2016/02/26/Android-App-secure-ssl.html
起因
前段时间,同事拿着一个代码安全扫描出来的 bug 过来咨询,我一看原来是个 https
通信时数字证书校验的漏洞,一想就明白了大概;其实这种问题早两年就有大规模的暴露,各大厂商App
也纷纷中招,想不到过了这么久天猫客户端里还留有这种坑;然后仔细研究了漏洞所在的代码片段,原来所属的是新浪微博分享 sdk 内部的,因为这个 sdk
是源码引用的,一直没有更新,年久失修,所以也就被扫描出来了。因此给出的解决方案是:
先获取最新的 sdk,看其内部是否已解决,已解决的话升级 sdk 版本即可;
第1步行不通,那就自己写校验逻辑,猫客全局通信基本已经使用 https 通信,参考着再写一遍校验逻辑也不是问题;
后来查了一下网上信息,早在2014年10月份,乌云
平台里就已经暴露过天猫这个漏洞,想必当时一定是忙于双十一忽略了这个问题。
虽然这个问题通过升级 sdk
解决了,但是这个问题纯粹是由于开发者本身疏忽造成的;特别是对于初级开发人员来说,可能为了解决异常,屏蔽了校验逻辑;所以我还是抽空再 review
了一下这个漏洞,整理相关信息。
漏洞描述
对于数字证书相关概念、Android 里 https 通信代码就不再复述了,直接讲问题。缺少相应的安全校验很容易导致中间人攻击,而漏洞的形式主要有以下3种:
自定义X509TrustManager
在使用HttpsURLConnection发起 HTTPS 请求的时候,提供了一个自定义的X509TrustManager,未实现安全校验逻辑,下面片段就是当时新浪微博 sdk 内部的代码片段。如果不提供自定义的X509TrustManager,代码运行起来可能会报异常(原因下文解释),初学者就很容易在不明真相的情况下提供了一个自定义的X509TrustManager,却忘记正确地实现相应的方法。本文重点介绍这种场景的处理方式。
TrustManager tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
//do nothing,接受任意客户端证书
}
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
//do nothing,接受任意服务端证书
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
sslContext.init(null, new TrustManager[] { tm }, null);
自定义了HostnameVerifier
在握手期间,如果 URL 的主机名和服务器的标识主机名不匹配,则验证机制可以回调此接口的实现程序来确定是否应该允许此连接。如果回调内实现不恰当,默认接受所有域名,则有安全风险。代码示例。
HostnameVerifier hnv = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
// Always return true,接受任意域名服务器
return true;
}
};
HttpsURLConnection.setDefaultHostnameVerifier(hnv);
信任所有主机名
SSLSocketFactory sf = new MySSLSocketFactory(trustStore);
sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
修复方案
分而治之,针对不同的漏洞点分别描述,这里就讲的修复方案主要是针对非浏览器App,非浏览器 App 的服务端通信对象比较固定,一般都是自家服务器,可以做很多特定场景的定制化校验。如果是浏览器 App,校验策略就有更通用一些。
自定义X509TrustManager。前面说到,当发起 HTTPS 请求时,可能抛起一个异常,以下面这段代码为例(来自官方文档):
try {
URL url = new URL("https://certs.cac.washington.edu/CAtest/");
URLConnection urlConnection = url.openConnection();
InputStream in = urlConnection.getInputStream();
copyInputStreamToOutputStream(in, System.out);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
private void copyInputStreamToOutputStream(InputStream in, PrintStream out) throws IOException {
byte[] buffer = new byte[1024];
int c = 0;
while ((c = in.read(buffer)) != -1) {
out.write(buffer, 0, c);
}
}
它会抛出一个SSLHandshakeException的异常。
javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:322)
at com.android.okhttp.Connection.upgradeToTls(Connection.java:201)
at com.android.okhttp.Connection.connect(Connection.java:155)
at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:276)
at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:211)
at com.android.okhttp.internal.http.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:382)
at com.android.okhttp.internal.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:332)
at com.android.okhttp.internal.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:199)
at com.android.okhttp.internal.http.DelegatingHttpsURLConnection.getInputStream(DelegatingHttpsURLConnection.java:210)
at com.android.okhttp.internal.http.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:25)
at me.longerian.abcandroid.datetimepicker.TestDateTimePickerActivity$1.run(TestDateTimePickerActivity.java:236)
Caused by: java.security.cert.CertificateException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
at com.android.org.conscrypt.TrustManagerImpl.checkTrusted(TrustManagerImpl.java:318)
at com.android.org.conscrypt.TrustManagerImpl.checkServerTrusted(TrustManagerImpl.java:219)
at com.android.org.conscrypt.Platform.checkServerTrusted(Platform.java:114)
at com.android.org.conscrypt.OpenSSLSocketImpl.verifyCertificateChain(OpenSSLSocketImpl.java:550)
at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:318)
... 10 more
Caused by: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
... 16 more
Android 手机有一套共享证书的机制,如果目标 URL 服务器下发的证书不在已信任的证书列表里,或者该证书是自签名的,不是由权威机构颁发,那么会出异常。对于我们这种非浏览器 app 来说,如果提示用户去下载安装证书,可能会显得比较诡异。幸好还可以通过自定义的验证机制让证书通过验证。验证的思路有两种:
方案1
不论是权威机构颁发的证书还是自签名的,打包一份到 app 内部,比如存放在 asset 里。通过这份内置的证书初始化一个KeyStore,然后用这个KeyStore去引导生成的TrustManager来提供验证,具体代码如下:
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
// uwca.crt 打包在 asset 中,该证书可以从https://itconnect.uw.edu/security/securing-computer/install/safari-os-x/下载
InputStream caInput = new BufferedInputStream(getAssets().open("uwca.crt"));
Certificate ca;
try {
ca = cf.generateCertificate(caInput);
Log.i("Longer", "ca=" + ((X509Certificate) ca).getSubjectDN());
Log.i("Longer", "key=" + ((X509Certificate) ca).getPublicKey();
} finally {
caInput.close();
}
// Create a KeyStore containing our trusted CAs
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
// Create an SSLContext that uses our TrustManager
SSLContext context = SSLContext.getInstance("TLSv1","AndroidOpenSSL");
context.init(null, tmf.getTrustManagers(), null);
URL url = new URL("https://certs.cac.washington.edu/CAtest/");
HttpsURLConnection urlConnection =
(HttpsURLConnection)url.openConnection();
urlConnection.setSSLSocketFactory(context.getSocketFactory());
InputStream in = urlConnection.getInputStream();
copyInputStreamToOutputStream(in, System.out);
} catch (CertificateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (NoSuchProviderException e) {
e.printStackTrace();
}
这样就可以得到正确的输出内容:
<html>
<head>
<title>UW Services CA Test Page</title>
</head>
<body>
<h2>
UW Services CA test page
</h2>
<p>
<b>QUESTION</b>:
Did you arrive here without any security alerts or warnings?</p>
<ul>
<p>
<li>
<b>YES</b> - This test page uses a certificate issued by the
UW Services Certificate Authority. If you reached this page
without any alerts or warnings from your browser, you
have successfully installed the UW Services CA Certificate
into your browser.
<p>
<li>
<b>NO</b> - If your browser warned you about the validity of this
test page's security certificate, or the certificate
authority is unrecognized, you may not have successfully
installed the UW Services CA Certificate.
<p>
</ul>
<form action="https://www.washington.edu/computing/ca/" method=get>
<input type=submit value="Return to the Install Page">
</form>
</body>
</html>
如果你用上述同样的代码访问 https://www.taobao.com/ 或者 https://www.baidu.com/ ,则会抛出那个SSLHandshakeException异常,也就是说对于特定证书生成的TrustManager,只能验证与特定服务器建立安全链接,这样就提高了安全性。如之前提到的,对于非浏览器 app 来说,这是可以接受的。
方案2
同方案1,打包一份到证书到 app 内部,但不通过KeyStore去引导生成的TrustManager,而是干脆直接自定义一个TrustManager,自己实现校验逻辑;校验逻辑主要包括:
•服务器证书是否过期
•证书签名是否合法
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
// uwca.crt 打包在 asset 中,该证书可以从https://itconnect.uw.edu/security/securing-computer/install/safari-os-x/下载
InputStream caInput = new BufferedInputStream(getAssets().open("uwca.crt"));
final Certificate ca;
try {
ca = cf.generateCertificate(caInput);
Log.i("Longer", "ca=" + ((X509Certificate) ca).getSubjectDN());
Log.i("Longer", "key=" + ((X509Certificate) ca).getPublicKey());
} finally {
caInput.close();
}
// Create an SSLContext that uses our TrustManager
SSLContext context = SSLContext.getInstance("TLSv1","AndroidOpenSSL");
context.init(null, new TrustManager[]{
new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain,
String authType)
throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain,
String authType)
throws CertificateException {
for (X509Certificate cert : chain) {
// Make sure that it hasn't expired.
cert.checkValidity();
// Verify the certificate's public key chain.
try {
cert.verify(((X509Certificate) ca).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];
}
}
}, null);
URL url = new URL("https://certs.cac.washington.edu/CAtest/");
HttpsURLConnection urlConnection =
(HttpsURLConnection)url.openConnection();
urlConnection.setSSLSocketFactory(context.getSocketFactory());
InputStream in = urlConnection.getInputStream();
copyInputStreamToOutputStream(in, System.out);
} catch (CertificateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (NoSuchProviderException e) {
e.printStackTrace();
}
同样上述代码只能访问 certs.cac.washington.edu 相关域名地址,如果访问 https://www.taobao.com/ 或者 https://www.baidu.com/ ,则会在cert.verify(((X509Certificate) ca).getPublicKey());处抛异常,导致连接失败。
•自定义HostnameVerifier,简单的话就是根据域名进行字符串匹配校验;业务复杂的话,还可以结合配置中心、白名单、黑名单、正则匹配等多级别动态校验;总体来说逻辑还是比较简单的,反正只要正确地实现那个方法。
HostnameVerifier hnv = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
//示例
if("yourhostname".equals(hostname)){
return true;
} else {
HostnameVerifier hv =
HttpsURLConnection.getDefaultHostnameVerifier();
return hv.verify(hostname, session);
}
}
};
•主机名验证策略改成严格模式
SSLSocketFactory sf = new MySSLSocketFactory(trustStore);
sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
原文链接:http://www.jianshu.com/p/7c1bc2daef8d
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
关于Android的https通讯安全的更多相关文章
- fiddler Android下https抓包全攻略
fiddler Android下https抓包全攻略 fiddler的http.https的抓包功能非常强大,可非常便捷得对包进行断点跟踪和回放,但是普通的配置对于像招商银行.支付宝.陌陌这样的APP ...
- Android进程间通讯
最近研究了一下Android进程间通讯,原来只是会用,但是只是会用是不行滴,就来研究一下. 刚开始看的时候,我的头是这么大,看了一夜的时候,头就变成这样了,,吓得宝宝赶紧上床休息了,. 先喝喝茶讲个故 ...
- 网络--三种网络通讯方式及Android的网络通讯机制
Android平台有三种网络接口可以使用,他们分别是:java.net.*(标准Java接口).Org.apache接口和Android.net.*(Android网络接口).下面分别介绍这些接口的功 ...
- Android组件的通讯——Intent
转载:Android组件的通讯-Intent 1.概述 一个应用程序的三个核心组件——activities.services.broadcast receivers,都是通过叫做intents的消息激 ...
- 深入理解HTTPS通讯原理
一.HTTPS简介 HTTPS(Hyper Text Transfer Protocol over Secure Socket Layer),简单来讲就是加了安全的HTTP,即HTTP+SSL:我们知 ...
- Android进阶笔记04:Android进程间通讯(IPC)之Messenger
一. Android进程间通讯之Messenger 的引入 (1)引言: 平时一说进程间通讯,大家都会想到AIDL,其实messenger和AIDL作用一样,都可以进行进程间通讯.它是基于消 ...
- android中跨进程通讯的4种方式
转自:http://blog.csdn.net/lyf_007217/article/details/8542359 帖子写的很好.看来一遍,试了一遍,感觉太有意义.必须转过来! android中跨进 ...
- https通讯原理
https通讯原理 HTTPS在传输数据之前需要客户端(浏览器)与服务端(网站)之间进行一次握手,在握手过程中将确立双方加密传输数据的密码信息.TLS/SSL协议不仅仅是一套加密传输的协议,更是一件经 ...
- Android蓝牙串口通讯【转】
本文转载自:http://blog.sina.com.cn/s/blog_631e3f2601012ixi.html Android蓝牙串口通讯 闲着无聊玩起了Android蓝牙模块与单片机蓝牙模块的 ...
随机推荐
- Android简易实战教程--第四十五话《几种对话框》
Android中提供了各种原生的对话框,在使用简单的功能的时候,还不比考虑自定义,使用原生的也能完成功能.本篇简单小案例就介绍三种对话框. 还是直接上代码吧: 布局中三个点击事件的按钮: <Li ...
- Python 函数参数传递机制.
learning python,5e中讲到.Python的函数参数传递机制是对象引用. Arguments are passed by assignment (object reference). I ...
- defaultdict的威力
>>> from collections import defaultdict >>> s='mmississippi' >>> d=defaul ...
- [sed]命令笔记
sed是linux下经常用到的工具,英文全名为stream editor. sed 在windows上的实现可以在这里找到 http://gnuwin32.sourceforge.net/packag ...
- UIKit中ImageView动画堆叠显示的微调整
大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 网上看到一个PackingList项目(如果需要源代码可以Q我 ...
- Android开发艺术探索——第二章:IPC机制(上)
Android开发艺术探索--第二章:IPC机制(上) 本章主要讲解Android的IPC机制,首先介绍Android中的多进程概念以及多进程开发模式中常见的注意事项,接着介绍Android中的序列化 ...
- 1026. Table Tennis (30)
题目如下: A table tennis club has N tables available to the public. The tables are numbered from 1 to N. ...
- OpenMP并行化实例----Mandelbrot集合并行化计算
在理想情况下,编译器使用自动并行化能够管理一切事务,使用OpenMP指令的一个优点是将并行性和算法分离,阅读代码时候无需考虑并行化是如何实现的.当然for循环是可以并行化处理的天然材料,满足一些约束的 ...
- Android初级教程通过简要分析“土司”源码,来自实现定义土司理论探讨
由于系统自带的土司瞬间即逝,而且非常难看.因此我们就希望自定义自己的土司风格.有些实例就是基于自定义土司完成的,例如金山卫士的火箭发射,基本原理就是个土司.但是在做出自己的土司风格之前,还是要简要分析 ...
- 后端分布式系列:分布式存储-MySQL 数据库事务与复制
好久没有写技术文章了,因为一直在思考 「后端分布式」这个系列到底怎么写才合适.最近基本想清楚了,「后端分布式」包括「分布式存储」和 「分布式计算」两大类.结合实际工作中碰到的问题,以寻找答案的方式来剖 ...