亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Android OKHttp源碼解析Https安全處理

 更新時(shí)間:2022年12月05日 15:09:33   作者:ZSAchg  
這篇文章主要為大家介紹了Android OKHttp源碼解析Https安全處理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

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 secretsession 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 secretsession 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 secrethash 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é)SSLSocketSSLSocket可以調(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í)OkHostnameVerifierverify方法實(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)文章

最新評(píng)論