Android OKHttp源碼解析Https安全處理
Https
Https是Http協(xié)議加上下一層的SSL/TSL協(xié)議組成的,TSL是SSL的后繼版本,差別很小,可以理解為一個(gè)東西。進(jìn)行Https連接時(shí),會(huì)先進(jìn)行TSL的握手,完成證書(shū)認(rèn)證操作,產(chǎn)生對(duì)稱(chēng)加密的公鑰、加密套件等參數(shù)。之后就可以使用這個(gè)公鑰進(jìn)行對(duì)稱(chēng)加密了。
Https的加密方式同時(shí)使用了非對(duì)稱(chēng)加密和對(duì)稱(chēng)加密:
- 使用反向的非對(duì)稱(chēng)加密對(duì)證書(shū)進(jìn)行簽名
- 在檢查通過(guò)的證書(shū)公鑰基礎(chǔ)上,利用非對(duì)稱(chēng)加密產(chǎn)生對(duì)稱(chēng)加密的公鑰
- 使用產(chǎn)生的公鑰,利用對(duì)稱(chēng)加密交互傳輸?shù)臄?shù)據(jù)。
上面就是Https工作的大致流程,下面詳細(xì)介紹下加密的知識(shí)和握手。
加密知識(shí)
對(duì)于加密這種技術(shù),很早很早之前就有了。沒(méi)有加密的數(shù)據(jù),稱(chēng)為明文,經(jīng)過(guò)加密的叫做密文。Http默認(rèn)都是明文傳輸,這種方式很容易被監(jiān)聽(tīng)或者修改。加密的最終目的,是保證機(jī)密性、完整性、可用性。
密碼是一套加密算法,使用計(jì)算機(jī)之前都是使用機(jī)械式后者密碼本進(jìn)行操作。在使用計(jì)算機(jī)之后,加密的安全程度愈來(lái)越高,但是被解密也愈來(lái)愈容易。
秘鑰
如果光有密碼和原始數(shù)據(jù),那么破解會(huì)簡(jiǎn)單很多,因?yàn)橹灰懒嗣艽a的加密方式,反向操作即可拿到明文數(shù)據(jù)。所以為了增加難度,增加了秘鑰。
現(xiàn)在密碼就需要兩個(gè)參數(shù)進(jìn)行計(jì)算了,秘鑰+明文+密碼==密文。只拿到密碼和密文,是不能獲取原始明文,還需要秘鑰。破解的難度就更大了。
用秘鑰加密的技術(shù),又因?yàn)榧用芎徒饷苊罔€的情況分兩種。
對(duì)稱(chēng)加密
加密和解密的秘鑰是相同的,這種加密方式被稱(chēng)為對(duì)稱(chēng)加密,使用的秘鑰被稱(chēng)為公鑰。
對(duì)稱(chēng)加密的速度很快,但是服務(wù)器需要把自己的公鑰傳到客戶(hù)端,客戶(hù)端使用這個(gè)公鑰對(duì)數(shù)據(jù)進(jìn)行加密,服務(wù)器使用同樣的公鑰進(jìn)行解密。
但是這種方式?jīng)]有辦法防止中間人攻擊,如果中間人篡改了傳輸?shù)墓€,使用自己的公鑰代替他,那么接可以截取發(fā)送方的數(shù)據(jù),使用自己的公鑰進(jìn)行解密。
非對(duì)稱(chēng)加密
加密和解密的秘鑰是不同的,這種加密方式被稱(chēng)為非對(duì)稱(chēng)加密,進(jìn)行加密的是公共的公鑰,而進(jìn)行解密的叫做私鑰。這樣只有私鑰的擁有者才可以解密數(shù)據(jù)。
反向的非對(duì)稱(chēng)加密是數(shù)字簽名,也就是使用私鑰進(jìn)行加密,使用公共的公鑰的進(jìn)行解密,這樣就可以鑒定發(fā)送者的身份。也就是只有發(fā)送者才有私鑰。
非對(duì)稱(chēng)加密的缺點(diǎn)是速度很慢,同樣也沒(méi)有辦法防止中間人攻擊,中間人截獲了服務(wù)器的公鑰。并用自己的公鑰代替,這樣也可以獲取發(fā)送者的數(shù)據(jù)。
Https的方案
因?yàn)閷?duì)稱(chēng)和非對(duì)稱(chēng)加密都有自己的問(wèn)題,都是因?yàn)楣€的傳遞沒(méi)法保證安全性,中間人可以通過(guò)替換成自己的公鑰,完成截取的工作。
Https使用了混合的方式,同時(shí)使用了兩種方式,使用非對(duì)稱(chēng)加密產(chǎn)生對(duì)稱(chēng)加密的公鑰,再通過(guò)對(duì)稱(chēng)加密進(jìn)行處理。首先對(duì)稱(chēng)加密比較快速相對(duì)于非對(duì)稱(chēng)加密。所以還是使用對(duì)稱(chēng)加密比較好,那么對(duì)稱(chēng)加密的缺點(diǎn)時(shí)怎么保證公鑰能夠安全的交換呢。
這里可以使用非對(duì)稱(chēng)加密傳輸這段公鑰。這樣這段公鑰就可以被安全的傳輸。因?yàn)橹挥蟹?wù)器的私鑰才可以進(jìn)行解密。非對(duì)稱(chēng)加密有什么問(wèn)題呢,就是不能判斷收到的公鑰是否就是真正的公鑰,是不是被篡改或者替換。怎么保證受到的公鑰就是合法的公鑰呢?
那就需要一個(gè)機(jī)構(gòu)來(lái)給這個(gè)公鑰背書(shū),可以通過(guò)它保證這個(gè)公鑰是合法的,而承載公鑰的載體就是證書(shū)??蛻?hù)端通過(guò)證書(shū)進(jìn)行驗(yàn)證,完成公鑰的獲取。之后就可以通過(guò)這個(gè)公鑰完成非對(duì)稱(chēng)加密傳輸。協(xié)商對(duì)稱(chēng)加密所用的公鑰。
Https的方案大體就是這樣。 以上就是Https的基礎(chǔ)知識(shí)。下面分析下TSL的握手細(xì)節(jié)。
TSL握手
TSL的握手主要的目的要協(xié)商加密的算法、對(duì)稱(chēng)加密的公鑰、TSL/SSL版本。
首先通過(guò)連接到服務(wù)器的443端口,通過(guò)TCP連接,這段是明文傳輸,用于溝通上面所說(shuō)的參數(shù)。建立完成連接后就可以開(kāi)始進(jìn)行握手操作了。
握手如上圖所示,逐條分析下
- 客戶(hù)端發(fā)送 client hello的報(bào)文,發(fā)送了客戶(hù)端支持的協(xié)議版本、密碼套件、隨機(jī)數(shù)、壓縮算法等,服務(wù)器要在這之中選中一個(gè)自己支持的,如果自己都不支持,那么就會(huì)斷開(kāi)連接。
- 服務(wù)器返回 server hello報(bào)文,內(nèi)含選中的版本、密碼套件、隨機(jī)數(shù)、壓縮算法等。并會(huì)返回自己的證書(shū)。
- 客戶(hù)端收到證書(shū)后,檢驗(yàn)這個(gè)證書(shū),檢驗(yàn)分四步:時(shí)間有效性檢查、簽發(fā)的頒發(fā)者可信度檢測(cè)、簽名檢測(cè)、站點(diǎn)名稱(chēng)檢測(cè)。如果四項(xiàng)檢測(cè)都通過(guò)了,那么就會(huì)取出證書(shū)中的公鑰。
- 通過(guò)上面產(chǎn)生的隨機(jī)數(shù),產(chǎn)生了Pre-master secret,該報(bào)文使用從證書(shū)中解密獲得的公鑰進(jìn)行加密(其實(shí)就是服務(wù)器的公鑰),并通過(guò)公鑰加密傳輸?shù)椒?wù)端。通過(guò)這個(gè)數(shù)通過(guò)DH算法計(jì)算出MAC報(bào)文摘要和對(duì)稱(chēng)加密的公鑰。 上面的方式就產(chǎn)生了可以進(jìn)行對(duì)稱(chēng)加密的公鑰。下面發(fā)送的數(shù)據(jù)就可以通過(guò)這個(gè)公鑰開(kāi)始對(duì)稱(chēng)加密了。
沒(méi)有用到數(shù)字證書(shū)? 傳輸?shù)淖C書(shū)使用了數(shù)字證書(shū)也就是反向的對(duì)稱(chēng)加密,當(dāng)收到證書(shū),檢驗(yàn)通過(guò)后,會(huì)使用CA的公鑰進(jìn)行檢測(cè),也就是CA使用了自己的私鑰進(jìn)行了加密,只有CA知道私鑰。
隨機(jī)數(shù)怎么計(jì)算的?可以參考這里
隨機(jī)數(shù)計(jì)算
傳輸過(guò)程中,會(huì)涉及3個(gè)隨機(jī)數(shù),客戶(hù)端產(chǎn)生的/服務(wù)端產(chǎn)生的/Pre-master secret。 在傳輸Pre-master secret時(shí),會(huì)使用從證書(shū)獲取的公開(kāi)秘鑰,只有服務(wù)器才可以解密,對(duì)于客戶(hù)端:
當(dāng)其生成了Pre-master secret之后,會(huì)結(jié)合原來(lái)的A、B隨機(jī)數(shù),用DH算法計(jì)算出一個(gè)master secret,緊接著根據(jù)這個(gè)master secret推導(dǎo)出hash secret和session secret。
對(duì)于服務(wù)端:
當(dāng)其解密獲得了Pre-master secret之后,會(huì)結(jié)合原來(lái)的A、B隨機(jī)數(shù),用DH算法計(jì)算出一個(gè)master secret,緊接著根據(jù)這個(gè)master secret推導(dǎo)出hash secret和session secret。
在客戶(hù)端和服務(wù)端的master secret是依據(jù)三個(gè)隨機(jī)數(shù)推導(dǎo)出來(lái)的,它是不會(huì)在網(wǎng)絡(luò)上傳輸?shù)模挥须p方知道,不會(huì)有第三者知道。同時(shí),客戶(hù)端推導(dǎo)出來(lái)的session secret和hash secret與服務(wù)端也是完全一樣的。
那么現(xiàn)在雙方如果開(kāi)始使用對(duì)稱(chēng)算法加密來(lái)進(jìn)行通訊,使用哪個(gè)作為共享的密鑰呢?過(guò)程是這樣子的:
雙方使用對(duì)稱(chēng)加密算法進(jìn)行加密,用hash secret對(duì)HTTP報(bào)文 做一次運(yùn)算生成一個(gè)MAC,附在HTTP報(bào)文的后面,然后用session-secret加密所有數(shù)據(jù)(HTTP+MAC),然后發(fā)送。
接收方則先用session-secret解密數(shù)據(jù),然后得到HTTP+MAC,再用相同的算法計(jì)算出自己的MAC,如果兩個(gè)MAC相等,證明數(shù)據(jù)沒(méi)有被篡改。
OkHttp的設(shè)計(jì)
OkHttp是支持自動(dòng)的Https連接的,也就是我們默認(rèn)訪問(wèn)一個(gè)Https的網(wǎng)站,會(huì)自動(dòng)的完成TSL的握手和加密。但是對(duì)于自簽名的證書(shū)還是需要我們進(jìn)行配置的。
涉及的類(lèi)
ConnectionSpec
:連接的參數(shù)配置,包括SSL/TLS的版本、密碼套件等,這個(gè)在OkHttpClient#connectionSpecs
進(jìn)行配置,默認(rèn)是具有SNI和ALPN等擴(kuò)展功能的現(xiàn)代TLS和clear text即明文傳輸。SSL握手的前兩部就是溝通這部分參數(shù)的。CertificateChainCleaner
:證書(shū)鏈清理工具,用于省略無(wú)用的證書(shū),過(guò)濾出一個(gè)列表,最后一個(gè)鏈結(jié)是受信任的證書(shū)。X509TrustManager
:此接口的實(shí)例管理哪些 X509 證書(shū)可用于驗(yàn)證安全套接字的遠(yuǎn)程端。 決策可能基于受信任的證書(shū)頒發(fā)機(jī)構(gòu)、證書(shū)撤銷(xiāo)列表、在線狀態(tài)檢查或其他方式。這個(gè)類(lèi)對(duì)應(yīng)上面證書(shū)檢測(cè)的簽發(fā)的頒發(fā)者可信度檢測(cè)、簽名檢測(cè)、時(shí)間有效性檢查。HostnameVerifier
:驗(yàn)證主機(jī)名是否與服務(wù)器的身份驗(yàn)證方案匹配??梢曰谧C書(shū),也可以基于其他方式。這個(gè)用于上面說(shuō)的驗(yàn)證證書(shū)的站點(diǎn)名稱(chēng)檢測(cè)。X509Certificate
:X.509 證書(shū)的抽象類(lèi)。 這提供了一種訪問(wèn) X.509 證書(shū)所有屬性的標(biāo)準(zhǔn)方法?,F(xiàn)有的證書(shū)都是X509類(lèi)型的,這時(shí)一個(gè)標(biāo)準(zhǔn)。SSLSocketFactory
:這個(gè)是jdk提供的工具,負(fù)責(zé)SSLSocket
,SSLSocket
可以調(diào)用handShake進(jìn)行ssl的握手。CertificatePinner
:固定證書(shū)配置,用于對(duì)握手通過(guò)的證書(shū)做固定驗(yàn)證,也就是證書(shū)必須滿(mǎn)足固定證書(shū)的配置。 上面的類(lèi)不但有jdk還有OkHttp的工具,共同完成了Https的工作。OkHttp大部分利用了jdk關(guān)注Https的支持。
OkHttpClient配置階段
OkHttpClient作為OkHttp的入口,可以對(duì)上面的類(lèi)進(jìn)行配置。看下在buidler里是怎么進(jìn)行配置的。
單獨(dú)配置SSLSocketFactory
設(shè)置用于保護(hù) HTTPS 連接的套接字工廠。如果未設(shè)置,將使用系統(tǒng)默認(rèn)值。
public Builder sslSocketFactory(SSLSocketFactory sslSocketFactory) { if (sslSocketFactory == null) throw new NullPointerException("sslSocketFactory == null"); this.sslSocketFactory = sslSocketFactory; this.certificateChainCleaner = Platform.get().buildCertificateChainCleaner(sslSocketFactory); return this; }
同時(shí)配置SSLSocketFactory和X509TrustManager
可以通過(guò)sslSocketFactory方法,配置上面的兩個(gè)參數(shù),正常情況下,我們不需要配置,只需要采用系統(tǒng)默認(rèn)的配置即可。
public Builder sslSocketFactory( SSLSocketFactory sslSocketFactory, X509TrustManager trustManager) { if (sslSocketFactory == null) throw new NullPointerException("sslSocketFactory == null"); if (trustManager == null) throw new NullPointerException("trustManager == null"); this.sslSocketFactory = sslSocketFactory; this.certificateChainCleaner = CertificateChainCleaner.get(trustManager); return this; }
配置HostnameVerifier
可以通過(guò)hostnameVerifier()配置hostnameVerifier,以達(dá)到我們檢測(cè)證書(shū)的站點(diǎn)名稱(chēng)。
public Builder hostnameVerifier(HostnameVerifier hostnameVerifier) { if (hostnameVerifier == null) throw new NullPointerException("hostnameVerifier == null"); this.hostnameVerifier = hostnameVerifier; return this; }
HostnameVerifier
是一個(gè)接口,我們只要調(diào)用它的verify
方法就可以完成校驗(yàn),這個(gè)操作發(fā)生在Https握手完成后,系統(tǒng)提供了AbstractVerifier
骨架類(lèi)進(jìn)行配置。默認(rèn)是OkHostnameVerifier
。
配置CertificatePinner
設(shè)置固定證書(shū),我們可以創(chuàng)建一個(gè)CertificatePinner,CertificatePinner是一個(gè)實(shí)現(xiàn)好的類(lèi),我們只要傳入主機(jī)名稱(chēng)和證書(shū)的SHA-256或者SHA-1 hashes即可。握手守信的證書(shū)必須通過(guò)配置的固定證書(shū)。如果不滿(mǎn)足,就會(huì)拋出異常,停止鏈接。
public Builder certificatePinner(CertificatePinner certificatePinner) { if (certificatePinner == null) throw new NullPointerException("certificatePinner == null"); this.certificatePinner = certificatePinner; return this; }
OkHttpClient參數(shù)處理階段
上面我們可以通過(guò)builder配置參數(shù),那么參數(shù)是如何進(jìn)行處理的呢。我們配置不配置一個(gè)參數(shù)又有什么不同呢?
boolean isTLS = false; for (ConnectionSpec spec : connectionSpecs) { isTLS = isTLS || spec.isTls(); } if (builder.sslSocketFactory != null || !isTLS) { // 自定義或不使用Https this.sslSocketFactory = builder.sslSocketFactory; this.certificateChainCleaner = builder.certificateChainCleaner; } else { // 默認(rèn)配置 X509TrustManager trustManager = Util.platformTrustManager(); this.sslSocketFactory = newSslSocketFactory(trustManager); this.certificateChainCleaner = CertificateChainCleaner.get(trustManager); } if (sslSocketFactory != null) { Platform.get().configureSslSocketFactory(sslSocketFactory); }
參數(shù)的處理代碼如上所示,先獲取鏈接的配置是否是TSL,除了明文連接外,都是使用TSL的。如果我們配置了自己的sslSocketFactory或者不是TSL連接(沒(méi)有配置sslSocketFactory),那么都會(huì)使用builde人內(nèi)部的sslSocketFactory。也就是說(shuō)配置了,就使用配置的,沒(méi)有配置,如果當(dāng)前不支持TSL,那么sslSocketFactory就為空。
如果沒(méi)有配置并且是TSL連接的話,這里就會(huì)使用默認(rèn)的配置。這里的邏輯是先獲取X509TrustManager,再通過(guò)X509TrustManager獲取SslSocketFactory,通過(guò)SslSocketFactory再獲取CertificateChainCleaner。整體的依賴(lài)關(guān)系就是這樣。后面配置自定義證書(shū)時(shí),也會(huì)使用這個(gè)依賴(lài)鏈。 依次看下每個(gè)過(guò)程:
X509TrustManager trustManager = Util.platformTrustManager();
public static X509TrustManager platformTrustManager() { try { TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init((KeyStore) null); TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers)); } return (X509TrustManager) trustManagers[0]; } catch (GeneralSecurityException e) { throw assertionError("No System TLS", e); // The system has no TLS. Just give up. } }
通過(guò)TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm());獲取默認(rèn)的TrustManager工廠,調(diào)用init方法,這個(gè)方法傳入秘鑰庫(kù),并使用證書(shū)頒發(fā)機(jī)構(gòu)和相關(guān)信任材料的來(lái)源初始化此工廠。通常使用傳入的KeyStore作為做出信任決策的基礎(chǔ)。我們自簽名的證書(shū)也會(huì)通過(guò)這個(gè)方法進(jìn)行配置。
后面只餓極取出trustManagerFactory.getTrustManagers(),拿數(shù)組第一個(gè)作為最終的X509TrustManager。
this.sslSocketFactory = newSslSocketFactory(trustManager);
private static SSLSocketFactory newSslSocketFactory(X509TrustManager trustManager) { try { SSLContext sslContext = Platform.get().getSSLContext(); sslContext.init(null, new TrustManager[] { trustManager }, null); return sslContext.getSocketFactory(); } catch (GeneralSecurityException e) { throw assertionError("No System TLS", e); // The system has no TLS. Just give up. } }
獲取X509TrustManager后,這里通過(guò)Platform.get().getSSLContext()獲取SSLContext。
Platform.get()通過(guò)反射獲取了不同平臺(tái)的配置工具,這樣OKHttp就可以運(yùn)行在不同的平臺(tái)上。獲取SSLContext后,就可以init方法,對(duì)SSLContext進(jìn)行配置,調(diào)用getSocketFactory獲取最終的SSLSocketFactory。
this.certificateChainCleaner = CertificateChainCleaner.get(trustManager);
這里配置了CertificateChainCleaner,獲取trustManager中的可以用于驗(yàn)證對(duì)等方的證書(shū),之后創(chuàng)建一個(gè)BasicCertificateChainCleaner
。
通過(guò)上面的兩個(gè)配置的步驟,就完成了配置階段,看看在連接時(shí)是怎么使用Https的。
OkHttp連接Https階段
Https的握手發(fā)生在Http連接之后,在ConnectInterceptor
這個(gè)連接攔截器中。在調(diào)用完connectSocket后,就開(kāi)始進(jìn)行SSL的握手。因?yàn)镠ttps需要默認(rèn)連接443 端口,但是Http會(huì)連接80端口,這個(gè)邏輯是在哪兒配置的呢。在我們構(gòu)建請(qǐng)求的Request傳入的HttpUrl中,有一個(gè)port字段就是用于確定端口的。在獲取端口時(shí),如果沒(méi)有進(jìn)行顯式的配置。就會(huì)根據(jù)defaultPort()
進(jìn)行配置。邏輯也比較簡(jiǎn)單。所以connectSocket會(huì)直接連接443端口,為下面的SSL握手做了準(zhǔn)備。
public static int defaultPort(String scheme) { if (scheme.equals("http")) { return 80; } else if (scheme.equals("https")) { return 443; } else { return -1; } }
進(jìn)行SSL連接主要通過(guò)connectTls
進(jìn)行。 通過(guò)下面的方法進(jìn)行判斷。如果sslSocketFactory不為null,那么就會(huì)使用Https進(jìn)行連接。
if (route.address().sslSocketFactory() == null)
private void connectTls(ConnectionSpecSelector connectionSpecSelector) throws IOException { Address address = route.address(); SSLSocketFactory sslSocketFactory = address.sslSocketFactory(); boolean success = false; SSLSocket sslSocket = null; try { // 創(chuàng)建SSLSocket,是對(duì)原始Socke的包裝 sslSocket = (SSLSocket) sslSocketFactory.createSocket( rawSocket, address.url().host(), address.url().port(), true /* autoClose */); // 配置SSL版本和密碼套件 ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket); if (connectionSpec.supportsTlsExtensions()) { // 配置SSL擴(kuò)展 Platform.get().configureTlsExtensions( sslSocket, address.url().host(), address.protocols()); } // 進(jìn)行握手 sslSocket.startHandshake(); // 等待握手完成 SSLSession sslSocketSession = sslSocket.getSession(); Handshake unverifiedHandshake = Handshake.get(sslSocketSession); // 進(jìn)行證書(shū)域名確定 if (!address.hostnameVerifier().verify(address.url().host(), sslSocketSession)) { List<Certificate> peerCertificates = unverifiedHandshake.peerCertificates(); if (!peerCertificates.isEmpty()) { X509Certificate cert = (X509Certificate) peerCertificates.get(0); throw new SSLPeerUnverifiedException( "Hostname " + address.url().host() + " not verified:" + "\n certificate: " + CertificatePinner.pin(cert) + "\n DN: " + cert.getSubjectDN().getName() + "\n subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert)); } else { throw new SSLPeerUnverifiedException( "Hostname " + address.url().host() + " not verified (no certificates)"); } } // 檢測(cè)固定證書(shū) address.certificatePinner().check(address.url().host(), unverifiedHandshake.peerCertificates()); // 握手成功,獲取Http協(xié)議 String maybeProtocol = connectionSpec.supportsTlsExtensions() ? Platform.get().getSelectedProtocol(sslSocket) : null; socket = sslSocket; source = Okio.buffer(Okio.source(socket)); sink = Okio.buffer(Okio.sink(socket)); handshake = unverifiedHandshake; protocol = maybeProtocol != null ? Protocol.get(maybeProtocol) : Protocol.HTTP_1_1; success = true; } catch (AssertionError e) { if (Util.isAndroidGetsocknameError(e)) throw new IOException(e); throw e; } finally { if (sslSocket != null) { Platform.get().afterHandshake(sslSocket); } if (!success) { closeQuietly(sslSocket); } } }
邏輯比較清晰,逐條分析下
- 通過(guò)
sslSocketFactory
創(chuàng)建SSLSocket
。通過(guò)SSLSocket
可以直接進(jìn)行執(zhí)行SSL的握手。 - 傳入上面講到的TSL版本和密碼套件
- 配置SSL的擴(kuò)展,這里如果
ALPN
的擴(kuò)展,會(huì)寫(xiě)上使用的Http版本,握完手后會(huì)取這個(gè)配置,并判斷是否使用Http2.0版本。 - 調(diào)用sslSocket.startHandshake(),進(jìn)行握手。這時(shí)一個(gè)同步的操作,會(huì)阻塞當(dāng)前線程,直到握手成功,如果中間出了什么問(wèn)題,那么會(huì)直接拋出異常。
- 完成握手后會(huì)獲取Handshake數(shù)據(jù),執(zhí)行到這里說(shuō)明握手已經(jīng)成功了,服務(wù)器的證書(shū)已經(jīng)被信任了。證實(shí)的信息就在Handshake中。
通過(guò)我們傳域名檢測(cè)完成域名檢測(cè),也就是hostnameVerifier
類(lèi),調(diào)用它的verify
方法,通過(guò)返回的boolean值,進(jìn)行判斷。默認(rèn)的值時(shí)OkHostnameVerifier
。verify
方法實(shí)現(xiàn)如下。這里檢測(cè)了host和ip的值。如果不一致,可能證書(shū)被替換了。
public boolean verify(String host, X509Certificate certificate) { return verifyAsIpAddress(host) ? verifyIpAddress(host, certificate) : verifyHostname(host, certificate); }
- 通過(guò)
CertificatePinner
固定證書(shū)檢測(cè),調(diào)用check
進(jìn)行檢測(cè)。如果當(dāng)前受信的證書(shū)不滿(mǎn)足固定的配置,那么就不能繼續(xù)請(qǐng)求。固定證書(shū)的威力很大,如果配置了,那么后續(xù)的版本必須滿(mǎn)足這個(gè)固定的配置,所以一直要商量好。 - 所有的檢查通過(guò),握手成功。這時(shí)就是獲取配置的時(shí)候了。比如商議的Http版本和連接成功的輸入輸出流,之后的傳輸,也會(huì)通過(guò)
SSlSocket
的輸入輸出進(jìn)行配置了。 以上就完成了SSL的握手和配置。
在實(shí)際應(yīng)用中我們可能需要配置自己的證書(shū),如果完全使用CA的證書(shū),我們是不需要配置什么的,使用默認(rèn)配置即可,但是還是有些場(chǎng)景需要自己動(dòng)手配置Https。最常見(jiàn)的情形就是配置自簽名的證書(shū),服務(wù)器給我們一個(gè)根證書(shū),我們配置在本地,在握手階段,服務(wù)器給出的證書(shū),會(huì)受這個(gè)根證書(shū)的認(rèn)證。這樣既完成了自簽名證書(shū)的配置。下面是一些場(chǎng)景和常用的OkHttp的代碼配置。
配置自簽名證書(shū)
信任所有證書(shū)
這是一種非常不安全的配置,這么配置,會(huì)導(dǎo)致毫無(wú)安全性可言。但是有些場(chǎng)景還是可以暫時(shí)使用的。
static class HttpsTrustAllCertsTrustManager implements X509TrustManager { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; //返回長(zhǎng)度為0的數(shù)組,相當(dāng)于return null } public static SSLSocketFactory createSSLSocketFactory() { SSLSocketFactory sSLSocketFactory = null; try { SSLContext sc = Platform.get().getSSLContext(); sc.init(null, new TrustManager[]{new HttpsTrustAllCertsTrustManager()},new SecureRandom()); sSLSocketFactory = sc.getSocketFactory(); } catch (Exception e) { } return sSLSocketFactory; } } static class TrustAllHostnameVerifier implements HostnameVerifier { @Override public boolean verify(String s, SSLSession sslSession) { return true; } } //構(gòu)建OkHttpClient OkHttpClient mClient = new OkHttpClient.Builder() .sslSocketFactory(HttpsTrustAllCertsTrustManager .createSSLSocketFactory(), new HttpsTrustAllCertsTrustManager()) .hostnameVerifier(new TrustAllHostnameVerifier()) .build();
上面共配置了兩個(gè)變量,SslSocketFactory和HostnameVerifier。
- 第一個(gè)變量依賴(lài)X509TrustManager。這個(gè)認(rèn)證中心,我們需要給一個(gè)空實(shí)現(xiàn),這樣就會(huì)信任所有的證書(shū),創(chuàng)建的模式和OkHttpClient創(chuàng)建默認(rèn)的配置套路一樣。
- 第二個(gè)HostnameVerifier,如果我們不進(jìn)行配置,會(huì)走一個(gè)默認(rèn)的
OkHostnameVerifier
,如果不設(shè)置也會(huì)驗(yàn)證域名。所以還需要實(shí)現(xiàn)一個(gè)自定義的驗(yàn)證期,永遠(yuǎn)返回true。
這樣經(jīng)過(guò)兩個(gè)兩步的設(shè)置,就完成了所有證書(shū)的配置工作。這種模式可以配合固定證書(shū)使用,也就是服務(wù)器的證書(shū)只能滿(mǎn)足固定的規(guī)則才可以,也不失是一種策略。
配置自簽名證書(shū)
對(duì)于自簽名的證書(shū),一般都是一個(gè)根證書(shū),服務(wù)器返回的證書(shū),使用這個(gè)根證書(shū)就可以進(jìn)行認(rèn)證。我們的任務(wù)就是配置這個(gè)默認(rèn)的自簽名證書(shū)進(jìn)入OkHttp的配置。
try { CertificateFactory cf = CertificateFactory.getInstance("X.509"); //獲取證書(shū)輸入流 InputStream caInput = null; Certificate ca; try { ca = cf.generateCertificate(caInput); } finally { caInput.close(); } // 創(chuàng)建KeyStore,穿入證書(shū) String keyStoreType = KeyStore.getDefaultType(); KeyStore keyStore = KeyStore.getInstance(keyStoreType); keyStore.load(null, null); keyStore.setCertificateEntry("ca", ca); // 創(chuàng)建TrustManagerFactory,用于生成TrustManager TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(keyStore); SSLContext s = SSLContext.getInstance("TLSv1", "AndroidOpenSSL"); s.init(null, tmf.getTrustManagers(), null); return s.getSocketFactory(); } catch (Exception e) { e.printStackTrace(); }
上面信任所有證書(shū),我們只是自己實(shí)現(xiàn)了一個(gè)TrustManager,但是在配置自簽名證書(shū)的時(shí)候,就需要通過(guò)TrustManagerFactory獲取了。和上面配置的主要區(qū)別,也在于TrustManager的創(chuàng)建。
- 獲取證書(shū)的輸入流,構(gòu)建Certificate
- 獲取KeyStore,通過(guò)傳入證書(shū)Certificate
- 創(chuàng)建TrustManagerFactory,并調(diào)用init,初始化KeyStore
- 通過(guò)SSLContext的init方法獲取SSLSocketFactory
通過(guò)傳入的SSLSocketFactory,傳入OkHttpClient就可以了。整體邏輯還是比較簡(jiǎn)單的。
以上就是Android OKHttp源碼解析Https安全處理的詳細(xì)內(nèi)容,更多關(guān)于Android OKHttp Https安全處理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android Flutter實(shí)現(xiàn)GIF動(dòng)畫(huà)效果的方法詳解
如果我們想對(duì)某個(gè)組件實(shí)現(xiàn)一組動(dòng)效應(yīng)該怎么辦呢?本文將利用Android Flutter實(shí)現(xiàn)GIF動(dòng)畫(huà)效果,文中的示例代碼講解詳細(xì),需要的可以參考一下2022-06-06Android實(shí)現(xiàn)橫屏切換科學(xué)計(jì)算器
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)橫屏切換科學(xué)計(jì)算器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-06-06Kotlin?協(xié)程的取消機(jī)制詳細(xì)解讀
這篇文章主要為大家介紹了Kotlin?協(xié)程的取消機(jī)制詳細(xì)解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10詳解Flutter?響應(yīng)式狀態(tài)管理框架GetX
這篇文章主要為大家介紹了Flutter?響應(yīng)式狀態(tài)管理框架GetX詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09Android上傳多張圖片的實(shí)例代碼(RxJava異步分發(fā))
本篇文章主要介紹了Android上傳多張圖片的實(shí)例代碼(RxJava異步分發(fā)),具有一定的參考價(jià)值,有興趣的可以了解一下2017-08-08Android實(shí)現(xiàn)長(zhǎng)按圖片保存至相冊(cè)功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)長(zhǎng)按圖片保存至相冊(cè)功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03Android圖片翻轉(zhuǎn)動(dòng)畫(huà)簡(jiǎn)易實(shí)現(xiàn)代碼
Android圖片翻轉(zhuǎn)動(dòng)畫(huà)效果如何實(shí)現(xiàn),本文將給你一個(gè)驚喜,實(shí)現(xiàn)代碼已經(jīng)列出,需要的朋友可以參考下2012-11-11Android中使用GridView進(jìn)行應(yīng)用程序UI布局的教程
GridView即平常我們見(jiàn)到的類(lèi)似九宮格的矩陣型布局,只不過(guò)默認(rèn)不帶分割線,這里我們就從基礎(chǔ)開(kāi)始來(lái)看一下Android中使用GridView進(jìn)行應(yīng)用程序UI布局的教程2016-06-06