漏洞描述
對於數字證書相關概念、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