通過上篇文章總體的了概述了AFSecurityPolicy的功能。
先了解下數字證書和原理,這篇文章非常詳細的講解了證書以及認證的原理。
下面繼續詳細分析AFSecurityPolicy驗證服務器信任的過程涉及的方法:
1:設置本地證書集合,和獲取本地證書的公鑰並創建公鑰集合
1.1設置本地證書集合
- (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;
}
NSLog(@"%@",publicKey);
[mutablePinnedPublicKeys addObject:publicKey];
}
self.pinnedPublicKeys = [NSSet setWithSet:mutablePinnedPublicKeys];
} else {
self.pinnedPublicKeys = nil;
}
}
1.2 獲取公鑰函數
static id AFPublicKeyForCertificate(NSData *certificate) {
id allowedPublicKey = nil;
SecCertificateRef allowedCertificate;
SecPolicyRef policy = nil;
SecTrustRef allowedTrust = nil;
SecTrustResultType result;
//DER編碼方式的X.509證書數據來創建SecCertificateRef
allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate);
//#ifndef __Require_Quiet
//#define __Require_Quiet(assertion, exceptionLabel) \
//do \
//{ \
//if ( __builtin_expect(!(assertion), 0) ) \
//{ \
//goto exceptionLabel; \
//} \
//} while ( 0 )
//#endif
//根據以上函數的邏輯,這個地方的含義是如果allowedCertificate 爲null則程序跳轉至_out片段繼續執行
__Require_Quiet(allowedCertificate != NULL, _out);
//創建X509格式的證書策略(此爲默認的策略)
policy = SecPolicyCreateBasicX509();
//#ifndef __Require_noErr_Quiet
//#define __Require_noErr_Quiet(errorCode, exceptionLabel) \
//do \
//{ \
//if ( __builtin_expect(0 != (errorCode), 0) ) \
//{ \
//goto exceptionLabel; \
//} \
//} while ( 0 )
//#endif
//根據以上函數的邏輯,創建SecTrust不成功(返回值 OSStatus 值爲非0)跳轉至_out片段繼續執行
__Require_noErr_Quiet(SecTrustCreateWithCertificates(allowedCertificate, policy, &allowedTrust), _out);
//SecTrustEvaluate是一個同步處理函數會阻塞線程,作用是評估證書信任,還有個異步函數SecTrustEvaluateAsync,評估結果是非0狀態的話直接跳轉至_out片段繼續執行
__Require_noErr_Quiet(SecTrustEvaluate(allowedTrust, &result), _out);
//獲公鑰
allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust);
_out:
if (allowedTrust) {
CFRelease(allowedTrust);
}
if (policy) {
CFRelease(policy);
}
if (allowedCertificate) {
CFRelease(allowedCertificate);
}
return allowedPublicKey;
}
2:iOS系統評估
static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {
// //對SecTrustResultType的解釋:
// typedef CF_ENUM(uint32_t, SecTrustResultType) {
// kSecTrustResultInvalid ,//顯示無效的設置或結果。這個結果通常意味着SecTrustEvaluate尚未被調用
// kSecTrustResultProceed ,//表明用戶可以繼續進行,也就意味着評估是通過的
// kSecTrustResultConfirm ,//在用戶需要繼續進行之前確認信息,此值官方已經不贊成使用,在OS X 10.9之後
// kSecTrustResultDeny ,//用戶配置拒絕繼續進行,也就意味着評估不通過
// kSecTrustResultUnspecified ,//評估成功證書是隱式信任的,但用戶不顯示的指定。
// kSecTrustResultRecoverableTrustFailure ,//表示信任策略失敗可以由用戶覆蓋
// kSecTrustResultFatalTrustFailure ,//表示信任失敗用戶不能覆蓋
// kSecTrustResultOtherError
// }
//iOS系統評估服務器信任
BOOL isValid = NO;
SecTrustResultType result;
//如果評估結果非0則爲驗證失敗,如果評估結果爲0,繼續判斷result
__Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out);
//如果result 不是這兩種kSecTrustResultUnspecified 和 kSecTrustResultProceed 則 視爲評估驗證失敗
isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
_out:
return isValid;
}
3:證書鏈
比如在AFNetworking的單元測試中拿到一個證書httpbin_0.cer,在windows操作系統下面打開會看到證書路徑:
此爲證書鏈信息,通過如下代碼得到證書鏈中的證書的公鑰信息:
NSString *domain = nil;//@"www.httpbin.org";
SecTrustRef serverTrust = AFUTTrustChainForCertsInDirectory(nil);
NSMutableArray *policies = [NSMutableArray array];
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
// (__bridge_transfer id)SecPolicyCreateBasicX509()
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust);
NSLog(@"%ld",certificateCount);
for (CFIndex i = 0; i < certificateCount; i++) {
SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i);
id publicKey = AFPublicKeyForCertificate((__bridge_transfer NSData *)SecCertificateCopyData(certificate));
NSLog(@"%@",publicKey);
}
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain
{
//驗證策略爲客戶端本地不做驗證交由系統驗證,或者客戶端本地不制定具體的證書並且允許無效的證書,同時還指定了域名,驗證域名,則視爲驗證不通過
if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
return NO;
}
NSMutableArray *policies = [NSMutableArray array];
//如果要驗證域名的話,就以域名爲參數創建一個 SecPolicyRef,這也是初始化默認設置的狀態
if (self.validatesDomainName) {
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
//創建一個符合 X509 標準的默認 SecPolicyRef 對象
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}
//設置服務信任策略
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
//下面開始基於設定的策略對證書進行驗證
//客戶端本地不驗證證書交由系統(iOS系統評估)
if (self.SSLPinningMode == AFSSLPinningModeNone) {
//允許無效證書則驗證通過,否則如果不允許無效證書交由系統去驗證
return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
} else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
//如果是系統驗證不通過 serverTrust,並且客戶端不允許無效的證書,本次驗證失敗
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);
//交由系統評估 serverTrust(實際上是驗證證書鏈)
if (!AFServerTrustIsValid(serverTrust)) {
return NO;
}
//如果serverTrust 經過系統評估通過,證書鏈驗證通過後,應該包含一個特定的證書(根證書)在證書鏈的最後位置
// obtain the chain after being validated, which *should* contain the pinned certificate in the last position (if it's the Root CA)
//獲取serverTrust中已簽名的證書鏈證書聚合
NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
//如果客戶端預置證書集合中包含了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);
//循環比較serverTrust中證書鏈中的公鑰集合和客戶端預置證書的公鑰集合,如果有相同的公鑰,並且公鑰個數大於1就驗證通過
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;
}
5:獲取服務信任證書鏈中的公鑰集合
//服務信任中的證書鏈的公鑰
static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) {
//創建符合X509標準的證書安全策略
SecPolicyRef policy = SecPolicyCreateBasicX509();
//得到證書鏈中要評估的證書個數
CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust);
NSMutableArray *trustChain = [NSMutableArray arrayWithCapacity:(NSUInteger)certificateCount];
for (CFIndex i = 0; i < certificateCount; i++) {
//根據索引獲取證書鏈中的證書
SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i);
// id publicKey = AFPublicKeyForCertificate((__bridge_transfer NSData *)SecCertificateCopyData(certificate));
// NSLog(@"%@",publicKey);
SecCertificateRef someCertificates[] = {certificate};
CFArrayRef certificates = CFArrayCreate(NULL, (const void **)someCertificates, 1, NULL);
SecTrustRef trust;
__Require_noErr_Quiet(SecTrustCreateWithCertificates(certificates, policy, &trust), _out);
SecTrustResultType result;
__Require_noErr_Quiet(SecTrustEvaluate(trust, &result), _out);
[trustChain addObject:(__bridge_transfer id)SecTrustCopyPublicKey(trust)];
_out:
if (trust) {
CFRelease(trust);
}
if (certificates) {
CFRelease(certificates);
}
continue;
}
CFRelease(policy);
return [NSArray arrayWithArray:trustChain];
}
6:獲取服務信任中證書鏈中的證書集合
//服務信任中的證書信任鏈
static NSArray * AFCertificateTrustChainForServerTrust(SecTrustRef serverTrust) {
//得到證書鏈中要評估的證書個數
CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust);
NSMutableArray *trustChain = [NSMutableArray arrayWithCapacity:(NSUInteger)certificateCount];
for (CFIndex i = 0; i < certificateCount; i++) {
SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i);
[trustChain addObject:(__bridge_transfer NSData *)SecCertificateCopyData(certificate)];
}
//返回證書鏈中要評估的證書
return [NSArray arrayWithArray:trustChain];
}