AFSecurityPolicy是AFNetworking中負責對https請求進行證書驗證的模塊,本文主要是要搞清楚它是如何工作的。 在介紹AFSecurityPolicy之前,我們先來瞭解一下https以及一些相關概念。 HTTPS 簡單來說,https是運行在SSL/TLS之上的http,是為 ...
AFSecurityPolicy是AFNetworking中負責對https請求進行證書驗證的模塊,本文主要是要搞清楚它是如何工作的。
在介紹AFSecurityPolicy之前,我們先來瞭解一下https以及一些相關概念。
HTTPS
簡單來說,https是運行在SSL/TLS之上的http,是為了提升數據傳輸的安全性的,使用到了對稱加密和非對稱加密演算法。讓我們通過客戶端與服務端的四次交互(四次握手)來詳細看看https都做了些什麼。
重點說下第2步和第3步。
第2步主要是要將服務端的公鑰安全的發送給客戶端,為此服務端主要做了兩件事:
1、用服務端的私鑰加密摘要
2、傳遞CA頒發的用CA的私鑰加密的包含服務端公鑰的證書
然後到了客戶端(也就是第3步),客戶端分幾步來驗證:
1、客戶端先使用本地CA來驗證證書是否授信,如果是權威機構頒發的證書,客戶端會認為該證書是授信的(如果是自己搭建的CA,則會被認為是不授信的,會產生警告),同時也會驗證證書的有效期,訪問的功能變數名稱是否一致等信息。
2、客戶端根據證書的要求生成證書編號
3、然後客戶端會用本地CA公鑰解密證書,拿到證書編號,如果生成的證書編號和拿到的證書編號一致,則認為證書沒有問題,從而拿到服務端的公鑰(簡稱為SK)
4、然後使用SK來解密服務端私鑰加密的摘要(簡稱SS),並且本地用加密演算法將內容加密成摘要(簡稱LS),對比SS == LS
需要說明一下CA的私鑰加密的包含服務端公鑰的證書,這個證書是用來保證信息不被中間人掉包的。因為證書編號是由CA的私鑰加密的,即使是中間人也無法拿到CA的私鑰,而客戶端的本地CA公鑰只能解密由CA的私鑰加密的證書編號,所以中間人無法偽造證書。
那麼假設中間人自己也申請一個CA的證書,然後客戶端請求的時候本來要請求服務端的證書A,中間人攔截以後,發回自己的證書B給客戶端,這個時候對於證書編號的驗證就不管用了,但是,證書A和證書B的功能變數名稱是不同的,所以客戶端在做驗證的時候,就會認為證書不授信。
以上這些繞來繞去的流程就是https請求保證數據安全和防篡改的簡易流程。也可以參考:
https://www.cnblogs.com/zhangshitong/p/6478721.html
我們在瞭解https的時候,會接觸到一些相關常見的概念,如:SSL/TLS,openssl,PKI,CA,X.509等,下麵我們來簡單瞭解一下。
HTTPS相關概念
SSL:(Secure Socket Layer,安全套接字層),用以保障在Internet上數據傳輸安全。
TLS:(Transport Layer Security,傳輸層安全協議),用於兩個應用程式之間提供保密性和數據完整性。簡單來看,可以認為TLS是SSL的升級版,比SSL更加安全。iOS9以後,已經要求TLS版本不低於1.2。
關於SSL和TLS的詳情,可以參看:
http://www.cocoachina.com/ios/20150918/13488.html
http://seanlook.com/2015/01/07/tls-ssl/
PKI:(Public Key Infrastructure,公開密鑰基礎設施),是一個標準,用以為所有網路應用提供加密和數字簽名等密碼服務及所必須的秘鑰和證書管理體系。CA是PKI的核心。
CA:(Certificate Authority,證書認證中心),是一個負責發放和管理數字證書的第三方權威機構,它負責管理PKI結構下的所有用戶(包括各種應用程式)的證書,把用戶的公鑰和用戶的其他信息捆綁在一起,在網上驗證用戶的身份。CA機構的數字簽名使得攻擊者不能偽造和篡改證書。前文所說的服務端證書,就是由CA頒發的。
關於PKI和CA的詳情,可以參看:
http://netsecurity.51cto.com/art/200602/21066.htm
X.509:是PKI體系中最重要的標準。是一些標準欄位的集合,這些欄位包含有關用戶或設備及其相應公鑰的信息。包含:版本號,公鑰,演算法,序列號,主題信息,有效期,認證機構,數字簽名等。可以參考:https://baike.baidu.com/item/x509/1240109?fr=aladdin
openssl:一個強大的安全套接字層密碼庫,大概可以分成三個主要的功能部分。
1、libcryto
,這是一個具有通用功能的加密庫,裡面實現了眾多的加密庫。
2、libssl
,這個是實現ssl機制的,它是用於實現TLS/SSL的功能。
3、openssl,是個多功能命令行工具,它可以實現加密解密,甚至還可以當CA來用,可以讓你創建證書、吊銷證書。
openssl生成私鑰,公鑰,並加密解密的簡單實用如下:
openssl genrsa -out rsakey0.pem 1024 //生成1024位rsa私鑰 openssl rsa -in rsakey0.pem -pubout -out rsaKeyPublic0 //生成公鑰 openssl rsautl -encrypt -in 1.txt -inkey rsaKeyPublic0.pem -pubin -out 2.txt //公鑰加密文件 openssl rsautl -decrypt -in 2.txt -inkey rsaKey0.pem -out 3.txt //私鑰解密文件
在瞭解了https的過程及相關的概念作為鋪墊以後,下麵我們來看看AFSecurityPolicy到底做了什麼。
AFSecurityPolicy
蘋果已經為我們封裝好了https連接的建立,加密解密的過程,但是並沒有為我們驗證證書是否合法,這一步需要在AFSecurityPolicy裡面完成。
當實用AFNetworking來發起https的請求時,會調用委托:
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler{ }
這是一個質詢,需要確認認證信息才能完成連接。
//判斷伺服器返回的證書類型,是否信任 if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) { credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; if (credential) { disposition = NSURLSessionAuthChallengeUseCredential;//使用指定的證書 } else { disposition = NSURLSessionAuthChallengePerformDefaultHandling;//預設處理方式(忽略證書) } } else { disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;//取消質詢 } } else { disposition = NSURLSessionAuthChallengePerformDefaultHandling; }
重點看下:
//判斷服務端來的證書是否驗證通過 - (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain { if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) { NSLog(@"In order to validate a domain name for self signed certificates, you MUST use pinning."); return NO; } NSMutableArray *policies = [NSMutableArray array]; //是否驗證功能變數名稱,如果你的請求要直接用ip去連,可以忽略功能變數名稱驗證,但是有風險,我們之前說過;下麵無論if還是else都是創建不同的驗證策略。 if (self.validatesDomainName) { [policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)]; } else { [policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()]; } //設置驗證策略 SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies); if (self.SSLPinningMode == AFSSLPinningModeNone) { //客戶端是否信任無效或過期的證書(可能是自簽名證書)或者校驗伺服器傳遞的安全信息 serverTrust 是否是有效。 return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust); } else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) { return NO; } switch (self.SSLPinningMode) { case AFSSLPinningModeNone: default: return NO; case AFSSLPinningModeCertificate: { //驗證證書是否和本地的證書相同 NSMutableArray *pinnedCertificates = [NSMutableArray array]; for (NSData *certificateData in self.pinnedCertificates) { [pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)]; } //將本地證書的數據設置為校驗伺服器安全信息 serverTrust 的錨證書 SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates); if (!AFServerTrustIsValid(serverTrust)) { return NO; } //獲取所有服務端的證書,後面用以比較是否包含 NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust); for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) { if ([self.pinnedCertificates containsObject:trustChainCertificate]) { return YES; } } return NO; } case AFSSLPinningModePublicKey: { //驗證本地證書的公鑰和服務端的公鑰是否相同 NSUInteger trustedPublicKeyCount = 0; //獲取服務端的證書公鑰 NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust); for (id trustChainPublicKey in publicKeys) { for (id pinnedPublicKey in self.pinnedPublicKeys) { if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) { trustedPublicKeyCount += 1; } } } return trustedPublicKeyCount > 0; } } return NO; }
註釋基本都加了,這裡要說一下:SSLPinningMode(校驗策略),分三種
AFSSLPinningModeNone: 這個模式表示不做SSL pinning,只跟瀏覽器一樣在系統的信任機構列表裡驗證服務端返回的證書。若證書是信任機構簽發的就會通過,若是自己伺服器生成的證書,這裡是不會通過的。
AFSSLPinningModeCertificate:這個模式表示用證書綁定方式驗證證書,需要客戶端保存有服務端的證書拷貝,這裡驗證分兩步,第一步驗證證書的功能變數名稱/有效期等信息,第二步是對比服務端返回的證書跟客戶端返回的是否一致。需要考慮證書過期的問題,如果過期了,要想辦法讓app發起一個http請求,將續費的證書下載到沙盒中就可以了。
AFSSLPinningModePublicKey:這個模式同樣是用證書綁定方式驗證,客戶端要有服務端的證書拷貝,只是驗證時只驗證證書里的公鑰,不驗證證書的有效期等信息。不需要考慮證書過期
我們來回顧一下AFSecurityPolicy的本地證書驗證和我們之前提的https請求本地驗證有什麼不同。
https本地驗證:是否權威機構的證書、功能變數名稱是否一致、證書編號是否一致,都一致的話,就可以拿到服務端的公鑰
AFSecurityPolicy驗證:是否權威機構證書、功能變數名稱是否一致(如果不驗證功能變數名稱,則忽略)、公鑰驗證或者證書驗證
綠色的部分就是有差異的地方,其實證書編號是否一致蘋果在底層已經做了。
如果選擇AFSSLPinningModeNone,則兩者是基本一致的,這也是預設策略。
但是如果選擇其他兩種,就表示在app內部放置了服務端的公鑰證書(因為一般app請求的功能變數名稱不會有太多,一般都是一個),這樣的話就需要比較公鑰證書或者公鑰本身了,所以會多出來一步。但是這樣做更加安全,對於防範中間人攻擊更有效,回顧一下本文https部分應該比較容易理解。
AFSecurityPolicy的參考文章:
https://blog.csdn.net/u011374318/article/details/79364995
https://www.cnblogs.com/oc-bowen/p/5896041.html
最後,當我們通過瞭解https的請求過程,瞭解相關知識,瞭解如何防範中間人攻擊,繞了這麼大一圈後,再來理解AFSecurityPolicy,會發覺容易很多。