此篇文章主要是記錄一下AFSecurityPolicy的學習過程, 在學習AFSecurityPolicy
之前對HTTP做了一些瞭解, 並做了兩篇筆記, 僅供參考.HTTP之網路基礎和HTTP通訊.
在開始之前先對HTTPS的認證流程做一下梳理.
- 客戶端發起網路請求, 會生成一個隨機數N1和支援的版本號以及加密方式等傳給伺服器.
- 伺服器接收到客戶端的請求, 生成一個隨機數N2, 將經過認證的數字證書和N2傳給客戶端.
- 客戶端接收到伺服器的證書, 驗證證書的真偽, 如果證書沒有問題, 就用伺服器的公鑰加密一個隨機數N3, 這個時候客戶端是知道N1, N2和N3的. 並將N3傳給伺服器.
- 伺服器接收到加密後的N3, 使用自己的私鑰將N3解密, 得到這個隨機數. 使用N1+N2+N3作為對稱金鑰開始通訊. 將加密後的資料傳給客戶端.
- 客戶端接收到加密後的資料, 使用對稱金鑰解密. 獲取到解密後的資料.
1. AFN中的使用
來看一下AFN中是如何使用AFSecurityPolicy
進行HTTPS認證的.
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
...
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
...
}
...
if (completionHandler) {
completionHandler(disposition, credential);
}
}
複製程式碼
在AFN中呼叫AFSecurityPolicy
的evaluateServerTrust:forDomain:
方法驗證伺服器證書.
2. AFSecurityPolicy的核心方法
我們來看一下evaluateServerTrust:forDomain:
方法.
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain
{
/*
allowInvalidCertificates:是否使用一個無效或者過期的證書(也就是使用自建證書)
validatesDomainName:驗證domain是否有效
*/
if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
// https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/OverridingSSLChainValidationCorrectly.html
// According to the docs, you should only trust your provided certs for evaluation.
// Pinned certificates are added to the trust. Without pinned certificates,
// there is nothing to evaluate against.
//
// From Apple Docs:
// "Do not implicitly trust self-signed certificates as anchors (kSecTrustOptionImplicitAnchors).
// Instead, add your own (self-signed) CA certificate to the list of trusted anchors."
NSLog(@"In order to validate a domain name for self signed certificates, you MUST use pinning.");
return NO;
}
// 這裡設定驗簽證書的策略
NSMutableArray *policies = [NSMutableArray array];
// 是否要驗籤domain域
if (self.validatesDomainName) {
// 如果需要驗證domain, 使用SecPolicyCreateSSL()建立驗證策略
// 第一個引數代表驗證整個證書鏈, 第二個引數為域名
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
// 如果不驗證domain, 就使用預設的驗證策略, Returns a policy object for the default X.509 policy.
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}
// 為serverTrust設定驗證策略, 告訴客戶端如何驗證serverTrust
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
if (self.SSLPinningMode == AFSSLPinningModeNone) {
// 如果SSLPinningMode==AFSSLPinningModeNone, 表示不適用SSL pinning, 但是允許自建證書或者使用AFServerTrustIsValid檢視serverTrust是否可信.
return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
} else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
// 即不允許自建證書, 並且serverTrust不可信, 就返回NO.
return NO;
}
switch (self.SSLPinningMode) {
case AFSSLPinningModeNone:
default:
return NO;
// 這種模式表示用證書繫結方式(SSL Pinning)驗證證書, 這裡需要客戶端儲存有伺服器的證書拷貝, 客戶端儲存的證書存在self.pinnedCertificates中
case AFSSLPinningModeCertificate: {
NSMutableArray *pinnedCertificates = [NSMutableArray array];
for (NSData *certificateData in self.pinnedCertificates) {
[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
}
// 設定錨點證書, 假如認證的證書是這個pinnedCertificates錨點證書的子節點, 那麼就信任該證書.
// 設定錨點證書是幹嘛的??????
SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);
if (!AFServerTrustIsValid(serverTrust)) {
return NO;
}
// obtain the chain after being validated, which *should* contain the pinned certificate in the last position (if it's the Root CA)
// 伺服器端的證書鏈, 倒序
NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
// 遍歷證書鏈, 檢視是否和客戶端的證書一直, 有的話說明服務端的證書可信.
for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
return YES;
}
}
return NO;
}
// 公鑰驗證, 這種模式值驗證公鑰, 不驗證證書的有效期等, 只要公鑰是正確的就可以
case AFSSLPinningModePublicKey: {
NSUInteger trustedPublicKeyCount = 0;
// 從serverTrust取出伺服器傳過來的所有可用的證書, 並取出對應的公鑰
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;
}
複製程式碼
在方法中, 加了一下自己理解的註釋, 如果有不對的地方, 望指正. 方法的作用是檢測伺服器的證書是否可以信任.
allowInvalidCertificates
是AFSecurityPolicy
的一個屬性, 是否允許信任一個無效證書(自建證書).
因為允許使用自建證書並且要驗證域名, 那麼SSLPinningMode的模式就不能為AFSSLPinningModeNone或者pinnedCertificates
證書個數不能為0.
SSLPinningMode
是一個列舉型別.
AFSSLPinningModeNone
這種模式表示不適用SSL Pinning, 如果證書是CA認證機構頒發的證書, 就通過認證, 否則不通過.AFSSLPinningModePublicKey
這種模式是證書繫結模式驗證證書, 客戶端要儲存證書的副本, 只驗證證書的公鑰.AFSSLPinningModeCertificate
這種模式是證書繫結模式驗證證書, 客戶端要儲存證書的副本, 首先需要驗證伺服器證書的有效期, 身份資訊等. 然後再將伺服器證書和本地證書作比較, 檢視是否一致.
pinnedCertificates
客戶端儲存的伺服器證書集合.
SecTrustSetPolicies
是為serverTrust
設定驗證策略, 以哪種方式驗證serverTrust
.
使用AFSSLPinningModeNone
模式驗證證書, 如果是允許自建證書就認為證書是值得信任的. AFServerTrustIsValid
是AFN自定義的函式, 驗證serverTrust
是否可信, 這個函式會在下邊講解. 如果serverTrust
既不可信又不允許使用自建證書就表示此證書不可信.
使用AFSSLPinningModeCertificate
模式驗證證書, 先遍歷pinnedCertificates
獲取客戶端儲存的證書, 並且使用SecTrustSetAnchorCertificates
將證書集合設定為錨點證書, 這部分我也不太瞭解. 使用AFCertificateTrustChainForServerTrust
方法獲取伺服器證書鏈, 獲取到的證書是倒敘集合, 遍歷這個集合, 如果本地證書集合包含伺服器證書鏈中的證書, 說明這個證書是可信的.
使用AFSSLPinningModePublicKey
模式驗證證書, 使用AFPublicKeyTrustChainForServerTrust
方法獲取伺服器所有可用證書對應的公鑰並存入集合中, 兩層for迴圈遍歷伺服器證書對應的公鑰和客戶端儲存的公鑰集合作比對, 如果證書公鑰相同就將trustedPublicKeyCount
信任公鑰個數加1, 如果trustedPublicKeyCount
個數大於0, 伺服器公鑰和客戶端公鑰有相同公鑰, 表明證書可信.
3. AFSecurityPolicy中的輔助函式
AFSecurityPolicy
中定義了一個函式供內部使用.
3.1 AFServerTrustIsValid函式
static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {
// 預設無效
BOOL isValid = NO;
// 列舉型別, 用來儲存結果
SecTrustResultType result;
// 如果SecTrustEvaluate(serverTrust, &result)的結果為0, 就繼續執行, 如果不為0就執行_out.
// SecTrustEvaluate:評估serverTrust
__Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out);
// 如果是kSecTrustResultUnspecified 表示評估成功, 證書可以信任.
// 如果是kSecTrustResultProceed 表示評估成功.
isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
_out:
return isValid;
}
複製程式碼
方法的作用是判斷serverTrust是否有效, 程式碼中已經加了註釋.
__Require_noErr_Quiet
是一個巨集定義, 使用到了goto語法, 當第一個引數不為0的時候就執行第二個引數指定的標籤.
SecTrustEvaluate
是系統的方法, 驗證serverTrust
, 並將驗證結果賦值給第二個引數result
.
3.2 AFCertificateTrustChainForServerTrust函式
static NSArray * AFCertificateTrustChainForServerTrust(SecTrustRef serverTrust) {
// 使用SecTrustGetCertificateCount
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];
}
複製程式碼
獲取serverTrust相關的證書鏈
3.3 AFPublicKeyTrustChainForServerTrust函式
static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) {
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);
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];
}
複製程式碼
獲取serverTrust證書鏈公鑰