iOS 中對 HTTPS 證書鏈的驗證
iOS 中對 HTTPS 證書鏈的驗證 這篇文章是我一邊學(xué)習(xí)證書驗證一邊記錄的內(nèi)容, 稍微整理了下,共扯了三部分內(nèi)容:1.2. 3. HTTPS 簡要原理; 數(shù)字證書的內(nèi)容、生成及驗證; iOS
iOS 中對 HTTPS 證書鏈的驗證 這篇文章是我一邊學(xué)習(xí)證書驗證一邊記錄的內(nèi)容, 稍微整理了下,共扯了三部分內(nèi)容:
1.
2. 3. HTTPS 簡要原理; 數(shù)字證書的內(nèi)容、生成及驗證; iOS 上對證書鏈的驗證。 HTTPS 概要
HTTPS 是運行在 TLS/SSL 之上的 HTTP,與普通的 HTTP 相比,在數(shù)據(jù)傳輸?shù)陌踩陨嫌泻艽蟮奶嵘?要了解它安全性的巧妙之處,需要先簡單地了解對稱加密和非對稱加密的區(qū)別: 對稱加密只有一個密鑰,加密和解密都用這個密鑰;
非對稱加密有公鑰和私鑰,私鑰加密后的內(nèi)容只有公鑰才能解密,公鑰加密的內(nèi)容只有私鑰才能解密。
為了提高安全性,我們常用的做法是使用對稱加密的手段加密數(shù)據(jù)。可是只使用對稱加密的話,雙方通信的開始總會以明文的方式傳輸密鑰。那么從一開始這個密鑰就泄露了,談不上什么安全。所以 TLS/SSL 在握手的階段,結(jié)合非對稱加密的手段,保證只有通信雙方才知道對稱加密的密鑰。大概的流程如下: ? ?
,

TSL:SSL_handshake.png
所以,HTTPS 實現(xiàn)傳輸安全的關(guān)鍵是:在 TLS/SSL 握手階段保證僅有通信雙方得到 Session Key!
數(shù)字證書的內(nèi)容
X.509 應(yīng)該是比較流行的 SSL 數(shù)字證書標(biāo)準(zhǔn),包含(但不限于)以下的字段:
,
下圖為 Wikipedia 的公鑰證書:
,
wikipedia_cer.png
數(shù)字證書的生成及驗證
數(shù)字證書的生成是分層級的,下一級的證書需要其上一級證書的私鑰簽名。 所以后者是前者的證書頒發(fā)者,也就是說上一級證書的 Subject Name 是其下一級證書的 Issuer Name。
,在得到證書申請者的一些必要信息(對象名稱,公鑰私鑰)之后,證書頒發(fā)者通過 SHA-256 哈希得到證書內(nèi)容的摘要,再用自己的私鑰給這份摘要加密,得到數(shù)字簽名。綜合已有的信息,生成分別包含公鑰和私鑰的兩個證書。 扯到這里,就有幾個問題:
問:如果說發(fā)布一個數(shù)字證書必須要有上一級證書的私鑰加密,那么最頂端的證書——根證書怎么來的? 根證書是自簽名的,即用自己的私鑰簽名,不需要其他證書的私鑰
來生成簽名。
問:怎么驗證證書是有沒被篡改? 當(dāng)客戶端走 HTTPS 訪問站點時,服務(wù)器會返回整個證書鏈。以下圖的證書鏈為例:

chain_hierarchy.png
要驗證 *.wikipedia.org 這個證書有沒被篡改,就要用
到 GlobalSign Organization Validation CA - SHA256 - G2 提
供的公鑰解密前者的簽名得到摘要 Digest1,我們的客戶端也計算
前者證書的內(nèi)容得到摘要 Digest2。對比這兩個摘要就能知道前者
是否被篡改。后者同理,使用 GlobalSign Root CA 提供的公鑰
驗證。當(dāng)驗證到到受信任的根證書時,就能確
定 *.wikipedia.org 這個證書是可信的。
問:為什么上面那個根證書 GlobalSign Root CA 是受信任的?
數(shù)字證書認(rèn)證機(jī)構(gòu)(Certificate Authority, CA)簽署和管理
的 CA 根證書,會被納入到你的瀏覽器和操作系統(tǒng)的可信證書列
表中,并由這個列表判斷根證書是否可信。所以不要隨便導(dǎo)入奇奇
怪怪的根證書到你的操作系統(tǒng)中。
問:生成的數(shù)字證書(如 *.wikipedia.org)都可用來簽署新的證書嗎?
不一定。如下圖,拓展字段里面有個叫 Basic Constraints 的數(shù)
據(jù)結(jié)構(gòu),里面有個字段叫路徑長度約束(Path Length Constraint ),
表明了該證書能繼續(xù)簽署 CA 子證書的深度,這里為0,說明這
個 GlobalSign Organization Validation CA - SHA256 - G2 只
能簽署客戶端證書,而客戶端證書不能用于簽署新的證書,CA 子
證書才能這么做。
,
path_length_constraint.png
iOS 上對證書鏈的驗證
在 Overriding TLS Chain Validation Correctly 中提到:
When a TLS certificate is verified, the operating system
verifies its chain of trust. If that chain of trust contains
only valid certificates and ends at a known (trusted) anchor
certificate, then the certificate is considered valid.
所以在 iOS 中,證書是否有效的標(biāo)準(zhǔn)是:
信任鏈中如果只含有有效證書并且以可信錨點(trusted anchor)結(jié)尾,那么這個證書就被認(rèn)為是有效的。
其中可信錨點指的是系統(tǒng)隱式信任的證書,通常是包括在系統(tǒng)中的 CA 根證書。不過你也可以在驗證證書鏈時,設(shè)置自定義的證書作為可信的錨點。
,NSURLSession 實現(xiàn) HTTPS
具體到使用 NSURLSession 走 HTTPS 訪問網(wǎng)站,-URLSession:didReceiveChallenge:completionHandler: 回調(diào)中會收到一個 challenge ,也就是質(zhì)詢,需要你提供認(rèn)證信息才能完成連接。這時候可以通過 challenge.protectionSpace.authenticationMethod 取得保護(hù)空間要求我們認(rèn)證的方式,如果這個值是 NSURLAuthenticationMethodServerTrust 的話,我們就可以插手 TLS 握手中“驗證數(shù)字證書有效性”這一步。
默認(rèn)的實現(xiàn)
系統(tǒng)的默認(rèn)實現(xiàn)(也即代理不實現(xiàn)這個方法)是驗證這個信任鏈,結(jié)果是有效的話則根據(jù) serverTrust 創(chuàng)建 credential 用于同服務(wù)端確立 SSL 連接。否則會得到 “The certificate for this server is invalid...”

這樣的錯誤而無法訪問。
比如在訪問 https://www-google-com 的時候咧,我們不實現(xiàn)這個方法也能訪問成功的。系統(tǒng)對 Google 服務(wù)器返回來的證書鏈,從葉節(jié)點證書往根證書層層驗證(有效期、簽名等等),遇到根證書時,發(fā)現(xiàn)作為可信錨點的它存在與可信證書列表中,那么驗證就通過,允許與服務(wù)端建立連接。
google.png
而當(dāng)我們訪問 https://www-12306-cn 時,就會出現(xiàn) "The certificate for this server is invalid. You might be connecting to a server that is pretending to be “www-12306-cn ” which could put your confidential
,information at risk." 的錯誤。原因就是系統(tǒng)在驗證到根證書時,發(fā)現(xiàn)它是自簽名、不可信的。

12306.png
自定義實現(xiàn)
如果我們要實現(xiàn)這個代理方法的話,需要提供 NSURLSessionAuthChallengeDisposition (處置方式)和 NSURLCredential(資格認(rèn)證)這兩個參數(shù)給 completionHandler 這個 block:
-(void )URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposit ion ,
NSURLCredential * _Nullable))completionHandler {
// 如果使用默認(rèn)的處置方式,那么 credential 就會被忽略
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthCh allengePerformDefaultHandling ;
,NSURLCredential *credential = nil;
if ([challenge.protectionSpace.authenticationMethod
isEqualToString:
NSURLAuthenticationMethodServerTrust ]) {
/* 調(diào)用自定義的驗證過程 */
if ([self myCustomValidation:challenge]) {
credential = [NSURLCredential credentialForTrust:challenge .protectionSpace.serverTrust ];
if (credential) {
disposition = NSURLSessionAuthChallengeUseCredential ; }
} else {
/* 無效的話,取消 */
disposition = NSURLSessionAuthChallengeCancelAuthenticati onChallenge
}
}
if (completionHandler) {
completionHandler(disposition, credential);
}
}
在 [self myCustomValidation:challenge] 調(diào)用自定義驗證過程,結(jié)果是有效的話才創(chuàng)建 credential 確立連接。
自定義的驗證過程,需要先拿出一個 SecTrustRef 對象,它是一種執(zhí)行信任鏈驗證的抽象實體,包含著驗證策略(SecPolicyRef )以及一系列受信任的錨點證書,而我們能做的也是修改這兩樣?xùn)|西而已。
,SecTrustRef trust = challenge.protectionSpace.serverTrust;
拿到 trust 對象之后,可以用下面這個函數(shù)對它進(jìn)行驗證。 static BOOL serverTrustIsVaild(SecTrustRef trust) { BOOL allowConnection = NO;
// 假設(shè)驗證結(jié)果是無效的 SecTrustResultType trustResult = kSecTrustResultInvalid;
// 函數(shù)的內(nèi)部遞歸地從葉節(jié)點證書到根證書的驗證 OSStatus statue = SecTrustEvaluate(trust, &trustResult);
if (statue == noErr) {
// kSecTrustResultUnspecified: 系統(tǒng)隱式地信任這個證書
// kSecTrustResultProceed: 用戶加入自己的信任錨點,顯式地告訴系統(tǒng)這個證書是值得信任的
allowConnection = (trustResult == kSecTrustResultProceed
|| trustResult == kSecTrustResultUnspecified);
}
return allowConnection;
}
這個函數(shù)什么時候調(diào)用完全取決于你的需求,如果你不想對驗證策略做修改而直接調(diào)用的話,那你居然還看到這里???(╯‵□′) ╯︵┻━┻
域名驗證
可以通過以下的代碼獲得當(dāng)前的驗證策略:
CFArrayRef policiesRef;
SecTrustCopyPolicies(trust , &policiesRef) ;