漏洞描述
对于数字证书相关概念、Android 里 https 通讯代码就再也不复述了,直接讲问题。缺乏相应的安全校验很容易致使中间人攻击,而漏洞的形式主要有如下3种:html
- 自定义
X509TrustManager
。
在使用HttpsURLConnection发起 HTTPS 请求的时候,提供了一个自定义的X509TrustManager,
未实现安全校验逻辑,下面片断就是常见的容易犯错的代码片断。若是不提供自定义的X509TrustManager,
代码运行起来可能会报异常(缘由下文解释),初学者就很容易在不明真相的状况下提供了一个自定义的X509TrustManager,
却忘记正确地实现相应的方法。本文重点介绍这种场景的处理方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
|
- 自定义了
HostnameVerifier
。
在握手期间,若是 URL 的主机名和服务器的标识主机名不匹配,则验证机制能够回调此接口的实现程序来肯定是否应该容许此链接。
若是回调内实现不恰当,默认接受全部域名,则有安全风险。代码示例。
修复方案
分而治之,针对不一样的漏洞点分别描述,这里就讲的修复方案主要是针对非浏览器App,非浏览器 App 的服务端通讯对象比较固定,通常都是自家服务器,能够作不少特定场景的定制化校验。若是是浏览器 App,校验策略就有更通用一些。java
- 自定义X509TrustManager。前面说到,当发起 HTTPS 请求时,可能抛起一个异常,如下面这段代码为例(来自官方文档):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
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
的异常。android
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
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来提供验证,具体代码以下:安全
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 37 38 39 40 41 42 43 44 45 46 47
|
try { CertificateFactory cf = CertificateFactory.getInstance("X.509"); |
方案2
同方案1,打包一份到证书到 app 内部,但不经过KeyStore
去引导生成的TrustManager
,而是干脆直接自定义一个TrustManager
,本身实现校验逻辑;校验逻辑主要包括:服务器
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
|
import android.content.Context;
import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.SignatureException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate;
import javax.net.ssl.X509TrustManager;
|
一样上述代码只能访问 certs.cac.washington.edu 相关域名地址,若是访问 https://www.taobao.com/ 或者 https://www.baidu.com/ ,则会在cert.verify(((X509Certificate) ca).getPublicKey());处抛异常,致使链接失败。session
- 自定义HostnameVerifier,简单的话就是根据域名进行字符串匹配校验;业务复杂的话,还能够结合配置中心、白名单、黑名单、正则匹配等多级别动态校验;整体来讲逻辑仍是比较简单的,反正只要正确地实现那个方法。
1 2 3 4 5 6 7 8 9 10 11 12 13
|
HostnameVerifier hnv = new HostnameVerifier() { @Override public boolean verify(String hostname, SSLSession session) { |
参考
苹果核 - Android App 安全的HTTPS 通讯
经过 HTTPS 和 SSL 确保安全app