前言:AFSecurityPolicy基于系統API? Security框架
1.AFN框架中實現HTTPS請求的客戶端校驗是通過AFSecurityPolicy對象實現的? ?
2.Security框架用于保證應用程序所管理之數據的安全。該框架提供的接口可用于管理證書、公鑰、私鑰以及信任策略。它支持生成加密的安全偽隨機數。同時,它也支持對證書和Keychain密鑰進行保存,是用戶敏感數據的安全倉庫。
HTTPS請求首先需要TLS/SSL握手
客戶端發出握手請求,請求報文主要包含協議版本號,客戶端提供的加密算法,一個隨機數random_Client。
服務端接收到請求,保存隨機數random_Client,然后發送響應給客戶端,包括選擇的加密算法、版本、壓縮算法、一個隨機數random_Server,以及證書鏈。
客戶端接收到信息,將隨機數random_Server保存,并且對返回的證書鏈進行校驗,如果檢驗不通過,終止連接。如果校驗通過產生隨機數字Pre_master,并用證書中的公鑰進行加密,將加密內容發送給服務器。同時客戶端根據random_Client、random_Server和Pre_master通過相應算法得到今后雙方通信的密鑰key??蛻舳诉壿嫿Y束。
服務端接收到公鑰加密的信息,通過證書的私鑰解密得到隨機數字Pre_master,然后根據random_Client、random_Server和Pre_master通過算法得到今后雙方通信的密鑰key。
握手完畢,客戶端和服務端通過生成的密鑰key和之前約定的對稱加密算法對通信過程的報文數據進行加密。
TLS/SSL握手的關鍵在于客戶端對服務器返回的證書進行驗證,比較有名的中間人攻擊就是通過偽造證書的方式竊取傳輸過程中加密的數據。
證書校驗
SSL證書是數字證書的一種類型,專門用于HTTPS類型的網絡請求,遵循X.509標準生成。SSL證書由CA(Certificate Authority)機構負責頒發,證書的申請流程如下:
申請者提供自己的必要信息(包括身份信息,公鑰、私鑰等)給CA機構。
CA機構認證申請者的信息。
認證通過后創建新證書,并通過哈希算法得到證書的摘要,用自己證書中的私鑰加密摘要,得到新證書的簽名。
當TLS/SSL握手時,服務端返回證書鏈,客戶端校驗證書的流程如下:
1.驗證證書的有效期(是否過期)、身份信息等。
2.驗證證書的簽名,首先用哈希算法計算證書的摘要1,然后用證書鏈的上一級證書的公鑰解密簽名,得到摘要2,然后比較摘要1和摘要2是否相等。
3.驗證證書頒發者的合法性,即驗證上一級證書的簽名,需要用再上一級證書的公鑰解密簽名,然后和哈希算法計算出的摘要進行比較。遞歸驗證,直到驗證根證書,由于根證書沒有上級證書,是最上級CA頒發的,是自簽名的。需要將根證書加入操作系統中作為信任證書。如果將證書鏈中某一級證書是被設置成了錨點證書,則被視為根證書。
AFSecurityPolicy
@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode; //校驗模式
@property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates; //本地綁定的證書
@property (nonatomic, assign) BOOL allowInvalidCertificates; //是否允許無效證書
@property (nonatomic, assign) BOOL validatesDomainName; //是否驗證域名
枚舉
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
? ? AFSSLPinningModeNone, //默認驗證方式
? ? AFSSLPinningModePublicKey, //比較證書的公鑰
? ? AFSSLPinningModeCertificate, //比較證書
};
校驗證書的方式有三種:
1.AFSSLPinningModeNone表示按照上文的方式驗證證書鏈
2.AF還提供了SSL Pinning的方式驗證,該方式把服務端下發的證書預先保存在APP的bundle中,然后通過比較服務端下發的證書和本地證書是否相同來校驗證書。AFSSLPinningModeCertificate采用SSL Pinning的方式,首先驗證服務器證書的有效期(是否過期)、身份信息等,然后將該證書和bundle中證書進行比較,是否一致。
3.AFSSLPinningModeCertificate同樣采用SSL Pinning的方式,但是不驗證證書的有效期等信息,同時只是比較兩個證書的公鑰是否一致。采用SSL Pinning的方式,本地bundle中導入的證書數據由pinnedCertificates維護。
AFSecurityPolicy還提供了允許無效證書驗證通過的開關allowInvalidCertificates,以及是否需要驗證證書域名的開關validatesDomainName。下面分析一下AFSecurityPolicy相關方法。
+ (NSSet*)certificatesInBundle:(NSBundle*)bundle;? 根據bundle的證書的初始化
+ (NSSet*)defaultPinnedCertificates; 默認證書
+ (instancetype)defaultPolicy; AFSecurityPolicy普通初始化? ?AFSSLPinningModeNone;
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet*)pinnedCertificates 根據pinnedCertificates證書與AFSSLPinningMode 初始化
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode;根據bundle證書與pinningMode初始化
- (void)setPinnedCertificates:(NSSet*)pinnedCertificates;? set方法
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString*)domain? ?
如果允許無效的證書,同時希望驗證證書的域名,則需要用SSL Pinning的方式驗證,即驗證證書的方式不能是AFSSLPinningModeNone,或者SSL Pinng需要本地導入證書,即pinnedCertificates數組不能為空。然后判斷域名是否需要驗證域名,如果需要,則將域名加入需要驗證的對象中
NSMutableArray *policies = [NSMutableArray array];
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) { //默認驗證方式
? ? return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust); //加驗證書
} else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
? ? return NO;
}
然后判斷驗證方式如果是AFSSLPinningModeNone且不允許無效證書,則調用AFServerTrustIsValid方法進行校驗
static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {
? ? BOOL isValid = NO;
? ? SecTrustResultType result;
? ? __Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out); //方法驗證
? ? isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
_out: //goto語句直接
? ? return isValid;
}
通過系統方法SecTrustEvaluate校驗證書,將校驗結果存儲在result中,同時通過__Require_noErr_Quiet宏來處理該方法返回error的情況:
如果該方法調用過程中失敗,即errorCode不為0,則通過goto語句跳轉,isValid直接返回NO。如果該方法調用成功,則根據result來判斷isValid是否為YES。當值為kSecTrustResultUnspecified或者kSecTrustResultProceed時,驗證通過。接下來處理AFSSLPinningModeCertificate的情況
case AFSSLPinningModeCertificate: {
? ? NSMutableArray *pinnedCertificates = [NSMutableArray array];
? ? for (NSData *certificateData in self.pinnedCertificates) {
? ? ? ? [pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
? ? }//將本地證書加入數組
? ? 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; //否則不通過
}
因為導入APP Bundle中的證書不是CA頒發的,不受信任,所以調用SecTrustSetAnchorCertificates方法將先將這些證書設置為serverTrust證書鏈上的錨點證書,類似于將這些證書設置為系統信任的根證書,然后調用AFServerTrustIsValid方法校驗serverTrust證書鏈時,如果遇到錨點證書,則終止驗證。然后調用AFCertificateTrustChainForServerTrust方法獲取serverTrust的證書鏈serverCertificates,遍歷證書鏈直到發現本地證書pinnedCertificates中有內容相同的證書,服務端下發的證書在本地認可的證書范圍內,校驗成功,如果沒有則校驗失敗。
接下來處理AFSSLPinningModePublicKey的方式,
case AFSSLPinningModePublicKey: {
? ? NSUInteger trustedPublicKeyCount = 0;
? ? NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust); //獲取serverTrust證書鏈的公鑰
? ? for (id trustChainPublicKey in publicKeys) { //匹配本地的證書公鑰和serverTrust的公鑰
? ? ? ? for (id pinnedPublicKey in self.pinnedPublicKeys) {
? ? ? ? ? ? if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
? ? ? ? ? ? ? ? trustedPublicKeyCount += 1;
? ? ? ? ? ? }
? ? ? ? }
? ? }
? ? return trustedPublicKeyCount > 0; //匹配成功,校驗成功
}
該方法首先獲取serverTrust證書鏈的公鑰,然后匹配本地的證書公鑰和serverTrust的公鑰,本地的公鑰通過self.pinnedPublicKeys屬性維護,在之前設置本地證書的方法中獲得
- (void)setPinnedCertificates:(NSSet *)pinnedCertificates {
? ? _pinnedCertificates = pinnedCertificates;
? ? if (self.pinnedCertificates) { //遍歷本地證書
? ? ? ? NSMutableSet *mutablePinnedPublicKeys = [NSMutableSet setWithCapacity:[self.pinnedCertificates count]];
? ? ? ? for (NSData *certificate in self.pinnedCertificates) {
? ? ? ? ? ? id publicKey = AFPublicKeyForCertificate(certificate); //獲取證書的公鑰
? ? ? ? ? ? if (!publicKey) {
? ? ? ? ? ? ? ? continue;
? ? ? ? ? ? }
? ? ? ? ? ? [mutablePinnedPublicKeys addObject:publicKey];
? ? ? ? }
? ? ? ? self.pinnedPublicKeys = [NSSet setWithSet:mutablePinnedPublicKeys]; //存放在pinnedPublicKeys屬性中
? ? } else {
? ? ? ? self.pinnedPublicKeys = nil;
? ? }
}
如果匹配成功,則返回校驗成功,否則失敗。匹配方法AFSecKeyIsEqualToKey調用isEqual:方法進行判斷。
總結
AFN框架的AFSecurityPolicy類為我們實現了HTTPS證書校驗的功能,且同時支持三種方式校驗證書,開發者可以根據不同情況進行選擇,如果是CA頒發的證書,開發者不用做額外邏輯,使用起來十分方便。
參考大神作品 :https://blog.csdn.net/panfeng200866/article/details/69662266