AFNetworking主要包含四個部分:1.Reachability
2.Security
3.Serialization
4.NSURLSession
其中前三個部分是彼此獨立的模組,互相無依賴,NSURLSession模組依賴於前三部分,現在我們就一個模組一個模組的分別解析,本文首先解析Security網路安全監測部分的原始碼。作為一個實用主義者,我本人是喜歡直接看程式碼,看不懂的地方再看解釋,所以下面會以程式碼為主。
AFSecurityPolicy.h原始碼
#import <Foundation/Foundation.h>
#import <Security/Security.h>
typedef NS_ENUM(NSInteger,AFSSLPinningMode) {
//代表無條件信任伺服器的證書,
AFSSLPinningModeNone,
//這個模式同樣是證書繫結方式驗證證書,需要客戶端保留伺服器的證書拷貝,只是驗證時只驗證證書的公鑰,不需要驗證證書的有效期等問題
AFSSLPinningModePublicKey,
//這個表示用證書繫結方式驗證證書,需要客戶端保留伺服器的證書拷貝,包括證書和公鑰兩部分,全部進行校驗
//跟客戶端儲存的是否一致
AFSSLPinningModeCertificate,
};
NS_ASSUME_NONNULL_BEGIN
/**
蘋果已經封裝了HTTPS連線的建立,資料的加密解密過程,我們直接可以訪問https網站,但蘋果並沒有驗證證書是否合法,無法避免中間人攻擊。要做到真正安全通訊,需要我們手動去驗證
服務端返回的證書,AFSecurityPolicy就是用來驗證HTTPS請求時證書是否正確。
*/
@interface AFSecurityPolicy : NSObject<NSSecureCoding,NSCopying>
//返回SSL Pinning的型別。預設的是AFSSLPinningModeNone。
@property(nonatomic,assign,readonly) AFSSLPinningMode SSLPinningMode;
//這個屬性儲存著所有的可用做校驗的證書的集合,AFNetworking預設會搜尋工程中所有.cer的證書檔案
//只要在證書集合中任何一個校驗通過,evaluateServerTrust:forDomain: 就會返回true,即通過校驗。
@property(nonatomic,strong,nullable) NSSet <NSData *>*pinnedCertificates;
/*
一般來說,每個版本的ios裝置中,都會包含一些既有的CA根證書,如果接收到的證書是iOS信任的根證書,那麼則為合法證書,否則則為非法證書,
自建證書就是非法的,所以自建證書要想通過驗證,需要這個這個屬性為YES,預設是NO
*/
@property(nonatomic,assign) BOOL allowInvalidCertificates;
//是否驗證證書中的域名domain,,這個可以嚴格的保證其安全性,預設為YES
@property(nonatomic,assign) BOOL validatesDomainName;
//使用此方法從bundle中獲取證書,和+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet <NSData *> *)pinnedCertificates;方法結合使用
+ (NSSet <NSData *> *)certificatesInBundle:(NSBundle *)bundle;
/**
預設的安全策略:AFSSLPinningModeNone
allowInvalidCertificates(不允許使用無效證書)
validatesDomainName(驗證域名CN)
不對證書和公鑰進行驗證
*/
+ (instancetype)defaultPolicy;
//根據指定的pinningMode建立並返回一個安全策略
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode;
//根據指定的pinningMode建立並返回一個安全策略
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet <NSData *> *)pinnedCertificates;
/**
核心方法,當伺服器響應需要進行證書驗證時,此方法會被呼叫。然後APP根據之前設定的驗證策略來判斷驗證是否通過
*/
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(nullable NSString *)domain;
@end
NS_ASSUME_NONNULL_END複製程式碼
AFSecurityPolicy.m原始碼
#import "AFSecurityPolicy.h"
#import <AssertMacros.h>
#if !TARGET_OS_IOS && !TARGET_OS_WATCH && !TARGET_OS_TV
//從公鑰中獲取資料
static NSData * AFSecKeyGetData(SecKeyRef key) {
CFDataRef data = NULL;
__Require_noErr_Quiet(SecItemExport(key, kSecFormatUnknown, kSecItemPemArmour, NULL, &data), _out);
return (__bridge_transfer NSData *)data;
_out:
if (data) {
CFRelease(data);
}
return nil;
}
#endif
//判斷兩個公鑰是否相同
static BOOL AFSecKeyIsEqualToKey(SecKeyRef key1, SecKeyRef key2) {
#if TARGET_OS_IOS || TARGET_OS_WATCH || TARGET_OS_TV
return [(__bridge id)key1 isEqual:(__bridge id)key2];
#else
return [AFSecKeyGetData(key1) isEqual:AFSecKeyGetData(key2)];
#endif
}
//從證書中獲取公鑰
static id AFPublicKeyForCertificate(NSData *certificate) {
id allowedPublicKey = nil;
SecCertificateRef allowedCertificate;
SecPolicyRef policy = nil;
SecTrustRef allowedTrust = nil;
SecTrustResultType result;
allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate);
__Require_Quiet(allowedCertificate != NULL, _out);
policy = SecPolicyCreateBasicX509();
__Require_noErr_Quiet(SecTrustCreateWithCertificates(allowedCertificate, policy, &allowedTrust), _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;
}
//驗證伺服器證書是否可信
static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {
BOOL isValid = NO;
SecTrustResultType result;
__Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out);
isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
_out:
return isValid;
}
//獲取伺服器返回的證書
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];
}
//獲取伺服器返回的證書的所有公鑰
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];
}
#pragma mark -
@interface AFSecurityPolicy ()
@property(nonatomic,assign,readwrite) AFSSLPinningMode SSLPinningMode;
//儲存本地證書的公鑰陣列
@property(nonatomic,strong,readwrite) NSSet *pinnedPublicKeys;
@end
@implementation AFSecurityPolicy
+(NSSet<NSData *> *)certificatesInBundle:(NSBundle *)bundle
{
NSArray *paths = [bundle pathsForResourcesOfType:@"cer" inDirectory:@"."];
NSMutableSet *certificates = [NSMutableSet setWithCapacity:[paths count]];
for (NSString *path in paths) {
NSData *certificateData = [NSData dataWithContentsOfFile:path];
[certificates addObject:certificateData];
}
return [NSSet setWithSet:certificates];
}
//AFNetworking預設會搜尋工程中所有.cer的證書檔案,你也可以選擇手動新增
+(NSSet *)defaultPinnedCertificates{
static NSSet *_defaultPinnedCertificates = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
_defaultPinnedCertificates = [self certificatesInBundle:bundle];
});
return _defaultPinnedCertificates;
}
+(instancetype)defaultPolicy
{
AFSecurityPolicy *securityPolicy = [[self alloc]init];
securityPolicy.SSLPinningMode = AFSSLPinningModeNone;
return securityPolicy;
}
+(instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode
{
return [self policyWithPinningMode:pinningMode withPinnedCertificates:[self defaultPinnedCertificates]];
}
+(instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet<NSData *> *)pinnedCertificates
{
AFSecurityPolicy *securityPolicy = [[self alloc] init];
securityPolicy.SSLPinningMode = pinningMode;
[securityPolicy setPinnedCertificates:pinnedCertificates];
return securityPolicy;
}- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
self.validatesDomainName = YES;
return self;
}
-(void)setPinnedCertificates:(NSSet<NSData *> *)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];
}else
{
self.pinnedPublicKeys = nil;
}
}
#pragma mark -
-(BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain
{
/*
判斷矛盾條件:
1.有域名,允許不合法證書,域名是否合法
2.因為要驗證域名,所以必須不能是後面兩種:AFSSLPinningModeNone或者是本地新增的證書為0
*/
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];
//需要驗證域名時,需要新增一個驗證域名的策略
if (self.validatesDomainName) {
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}
//為serverTrust設定驗證策略,即告訴客戶端如何驗證serverTrust
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
if (self.SSLPinningMode == AFSSLPinningModeNone) {
/*
SSLPinningMode為AFSSLPinningModeNone時,allowInvalidCertificates為YES,則代表伺服器任何證書都能驗證通過,也就是支援自簽名證書,如果不是自簽名
則要判斷該證書是否可信
*/
return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
} else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
//如果伺服器證書不是系統信任證書,且不允許不信任的證書通過則返回NO
return NO;
}
switch (self.SSLPinningMode) {
case AFSSLPinningModeNone:
default:
return NO;
case AFSSLPinningModeCertificate: {
/*
把證書data,用系統api轉成 SecCertificateRef 型別的資料,SecCertificateCreateWithData函式對原先的pinnedCertificates做一些處理,保證返回的證書都是DER編碼的X.509證書
*/
NSMutableArray *pinnedCertificates = [NSMutableArray array];
for (NSData *certificateData in self.pinnedCertificates) {
[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
}
/*
將pinnedCertificates設定成需要參與驗證的Anchor Certificate(錨點證書,通過SecTrustSetAnchorCertificates設定了參與校驗錨點證書之後,假如驗證的數字證書是這個錨點證書的子節點,即驗證的數字證書是由錨點證書對應CA或子CA簽發的,或是該證書本身,則信任該證書),具體就是呼叫SecTrustEvaluate來驗證。
//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: {
/*
公鑰驗證 AFSSLPinningModePublicKey模式同樣是用證書繫結(SSL Pinning)方式驗證,客戶端要有服務端的證書拷貝,只是驗證時只驗證證書裡的公鑰,不驗證證書的有效期等資訊。只要公鑰是正確的,就能保證通訊不會被竊聽,因為中間人沒有私鑰,無法解開通過公鑰加密的資料。
*/
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;
}
#pragma mark - NSKeyValueObserving
//鍵值依賴,當本地的證書改變時,證書的公鑰也改變了
+ (NSSet *)keyPathsForValuesAffectingPinnedPublicKeys {
return [NSSet setWithObject:@"pinnedCertificates"];
}
#pragma mark - NSSecureCoding--------------------------------------------------------------------------------------------------
//下面的內容是NSSecureCoding和NSCopying協議的內容
+ (BOOL)supportsSecureCoding {
return YES;
}
- (instancetype)initWithCoder:(NSCoder *)decoder {
self = [self init];
if (!self) {
return nil;
}
self.SSLPinningMode = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(SSLPinningMode))] unsignedIntegerValue];
self.allowInvalidCertificates = [decoder decodeBoolForKey:NSStringFromSelector(@selector(allowInvalidCertificates))];
self.validatesDomainName = [decoder decodeBoolForKey:NSStringFromSelector(@selector(validatesDomainName))];
self.pinnedCertificates = [decoder decodeObjectOfClass:[NSArray class] forKey:NSStringFromSelector(@selector(pinnedCertificates))];
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:[NSNumber numberWithUnsignedInteger:self.SSLPinningMode] forKey:NSStringFromSelector(@selector(SSLPinningMode))];
[coder encodeBool:self.allowInvalidCertificates forKey:NSStringFromSelector(@selector(allowInvalidCertificates))];
[coder encodeBool:self.validatesDomainName forKey:NSStringFromSelector(@selector(validatesDomainName))];
[coder encodeObject:self.pinnedCertificates forKey:NSStringFromSelector(@selector(pinnedCertificates))];
}
#pragma mark - NSCopying
- (instancetype)copyWithZone:(NSZone *)zone {
AFSecurityPolicy *securityPolicy = [[[self class] allocWithZone:zone] init];
securityPolicy.SSLPinningMode = self.SSLPinningMode;
securityPolicy.allowInvalidCertificates = self.allowInvalidCertificates;
securityPolicy.validatesDomainName = self.validatesDomainName;
securityPolicy.pinnedCertificates = [self.pinnedCertificates copyWithZone:zone];
return securityPolicy;
}
@end
複製程式碼
如何使用AFSecurityPolicy這個類進行安全策略設定:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
//allowInvalidCertificates 是否允許無效證書(也就是自建的證書),預設為NO
// 如果是需要驗證自建證書,需要設定為YES
securityPolicy.allowInvalidCertificates = YES;
/*
//validatesDomainName 是否需要驗證域名,預設為YES;
//假如證書的域名與你請求的域名不一致,需把該項設定為NO;如設成NO的話,即伺服器使用其他可信任機構頒發的證書,也可以建立連線,這個非常危險,建議開啟。
//置為NO,主要用於這種情況:客戶端請求的是子域名,而證書上的是另外一個域名。因為SSL證書上的域名是獨立的,假如證書上註冊的域名是www.google.com,那麼mail.google.com是無法驗證通過的;當然,有錢可以註冊萬用字元的域名*.google.com,但這個還是比較貴的。
*/
securityPolicy.validatesDomainName = YES;
return YES;
}複製程式碼
參考資料如下:
AFNetworking3.0原始碼解讀(5)AFSecurityPolicy
接下來呢打算先寫一下關於KVO的知識和HTTPS是如何建立通過加密建立安全連線的知識,原始碼解析先暫停一下下啦。