iOS之AFSecurityPolicy

AFSecurityPolicy是AFNetworking中負責對https請求進行證書驗證的模塊,本文主要是要搞清楚它是如何工做的。html

在介紹AFSecurityPolicy以前,咱們先來了解一下https以及一些相關概念。ios

HTTPS

簡單來講,https是運行在SSL/TLS之上的http,是爲了提高數據傳輸的安全性的,使用到了對稱加密和非對稱加密算法。讓咱們經過客戶端與服務端的四次交互(四次握手)來詳細看看https都作了些什麼。算法

重點說下第2步和第3步。瀏覽器

第2步主要是要將服務端的公鑰安全的發送給客戶端,爲此服務端主要作了兩件事:安全

一、用服務端的私鑰加密摘要服務器

二、傳遞CA頒發的用CA的私鑰加密的包含服務端公鑰的證書網絡

而後到了客戶端(也就是第3步),客戶端分幾步來驗證:session

一、客戶端先使用本地CA來驗證證書是否授信,若是是權威機構頒發的證書,客戶端會認爲該證書是授信的(若是是本身搭建的CA,則會被認爲是不授信的,會產生警告),同時也會驗證證書的有效期,訪問的域名是否一致等信息。app

二、客戶端根據證書的要求生成證書編號dom

三、而後客戶端會用本地CA公鑰解密證書,拿到證書編號,若是生成的證書編號和拿到的證書編號一致,則認爲證書沒有問題,從而拿到服務端的公鑰簡稱爲SK

四、而後使用SK來解密服務端私鑰加密的摘要(簡稱SS),而且本地用加密算法將內容加密成摘要(簡稱LS),對比SS == LS

須要說明一下CA的私鑰加密的包含服務端公鑰的證書,這個證書是用來保證信息不被中間人掉包的。由於證書編號是由CA的私鑰加密的,即便是中間人也沒法拿到CA的私鑰,而客戶端的本地CA公鑰只能解密由CA的私鑰加密的證書編號,因此中間人沒法僞造證書。 

那麼假設中間人本身也申請一個CA的證書,而後客戶端請求的時候原本要請求服務端的證書A,中間人攔截之後,發回本身的證書B給客戶端,這個時候對於證書編號的驗證就無論用了,可是,證書A和證書B的域名是不一樣的,因此客戶端在作驗證的時候,就會認爲證書不授信。

以上這些繞來繞去的流程就是https請求保證數據安全和防篡改的簡易流程。也能夠參考:

http://www.javashuo.com/article/p-snqwjvkn-bu.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:一個強大的安全套接字層密碼庫,大概能夠分紅三個主要的功能部分。

一、libcryto,這是一個具備通用功能的加密庫,裏面實現了衆多的加密庫。

二、libssl,這個是實現ssl機制的,它是用於實現TLS/SSL的功能。

三、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

http://www.javashuo.com/article/p-utnaydtp-er.html

 

最後,當咱們經過了解https的請求過程,瞭解相關知識,瞭解如何防範中間人攻擊,繞了這麼大一圈後,再來理解AFSecurityPolicy,會發覺容易不少。

相關文章
相關標籤/搜索