目錄
- 前言
- AFSecurityPolicy.h
- 對(duì)服務(wù)器證書的驗(yàn)證策略
- 屬性
- 獲取證書
- 自定義安全策略
- 核心方法
- AFSecurityPolicy.m
- SecTrustRef
- 從證書中提取公鑰
- 將公鑰轉(zhuǎn)化成NSData
- 比對(duì)兩個(gè)公鑰是否相同
- 返回服務(wù)器是否可以被信任
- 取出所有服務(wù)器返回的證書
- 取出服務(wù)器返回的所有證書中的公鑰
- AFSecurityPolicy對(duì)象方法
- 核心方法
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain
- API注釋Demo
- 參考資料
前言
AFNetworking源碼第三篇
主要看了看AFSecurityPolicy的內(nèi)容
負(fù)責(zé)網(wǎng)絡(luò)安全策略(證書)的驗(yàn)證
作為一個(gè)輔助模塊人灼、代碼量和文件都比較少
一行一行讀下來(lái)就可以了
但是最好把HTTP/HTTPS好好理解一下辨泳、這里就先不提了。將來(lái)看網(wǎng)絡(luò)協(xié)議的時(shí)候好好補(bǔ)一下喊崖。
AFN概述:《iOS源碼補(bǔ)完計(jì)劃--AFNetworking 3.1.0源碼研讀》
AFSecurityPolicy.h
對(duì)服務(wù)器證書的驗(yàn)證策略
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
AFSSLPinningModeNone,//無(wú)條件信任服務(wù)器的證書
AFSSLPinningModePublicKey,//對(duì)服務(wù)器返回的證書中的PublicKey進(jìn)行驗(yàn)證
AFSSLPinningModeCertificate,//對(duì)服務(wù)器返回的證書同本地證書全部進(jìn)行校驗(yàn)
};
屬性
/**
SSLPinning 默認(rèn) `AFSSLPinningModeNone`
*/
@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode;
/**
本地證書合集
默認(rèn)腿箩、將會(huì)從整個(gè)工程目錄下加載所有(.cer)的證書文件
如果想定制證書豪直、可以使用`certificatesInBundle`來(lái)加載證書
然后調(diào)用`policyWithPinningMode:withPinnedCertificates`來(lái)創(chuàng)建一個(gè)新`AFSecurityPolicy`對(duì)象用于驗(yàn)證
如果證書合集中任何一個(gè)被校驗(yàn)通過(guò)、那么`evaluateServerTrust:forDomain:`都將返回true
*/
@property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates;
/**
使用允許無(wú)效或過(guò)期的證書 默認(rèn)`NO`
*/
@property (nonatomic, assign) BOOL allowInvalidCertificates;
/**
是否驗(yàn)證域名 默認(rèn)`YES`
*/
@property (nonatomic, assign) BOOL validatesDomainName;
獲取證書
/**
從指定`bundle`中獲取證書合集
然后調(diào)用`policyWithPinningMode:withPinnedCertificates`來(lái)創(chuàng)建一個(gè)新`AFSecurityPolicy`對(duì)象用于驗(yàn)證
*/
+ (NSSet <NSData *> *)certificatesInBundle:(NSBundle *)bundle;
自定義安全策略
/**
默認(rèn)的安全策略
1珠移、不允許無(wú)效或過(guò)期的證書
2弓乙、驗(yàn)證域名
3末融、不對(duì)證書和公鑰進(jìn)行驗(yàn)證
*/
+ (instancetype)defaultPolicy;
///---------------------
/// @name Initialization
///---------------------
/**
通過(guò)指定的驗(yàn)證策略`AFSSLPinningMode`來(lái)創(chuàng)建
*/
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode;
/**
通過(guò)指定的驗(yàn)證策略`AFSSLPinningMode`、以及證書合集來(lái)創(chuàng)建
*/
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet <NSData *> *)pinnedCertificates;
核心方法
/**
根據(jù)具體配置唆貌、確定是否接受指定服務(wù)器的信任
服務(wù)器驗(yàn)證時(shí)會(huì)返回`NSURLCredential`challenge對(duì)象
@param serverTrust 使用challenge.protectionSpace.serverTrust參數(shù)即可
@param domain 使用challenge.protectionSpace.host即可
*/
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(nullable NSString *)domain;
AFSecurityPolicy.m
其實(shí)整個(gè).m文件也太多可以研究的地方滑潘、因?yàn)槎际枪潭ǖ姆椒āD阒荒苓@么寫~
不過(guò)锨咙、一行一行看一看语卤。iOS的證書到底是如何驗(yàn)證的、也不錯(cuò)酪刀。
-
SecTrustRef
整個(gè)驗(yàn)證都是基于SecTrustRef
的粹舵、和.cer
文件的關(guān)系大概是:
NSData格式的證書
=>SecCertificateRef
=>SecTrustRef
對(duì)象
這個(gè)SecTrustRef
通過(guò)
CFDataRef SecCertificateCopyData(SecCertificateRef certificate)
SecKeyRef SecTrustCopyPublicKey(SecTrustRef trust)
的方式又可以取出證書和公鑰可見。
SecTrustRef
就是一個(gè)內(nèi)部至少攜帶了證書與公鑰的結(jié)構(gòu)體骂倘。
-
從證書中提取公鑰
static id AFPublicKeyForCertificate(NSData *certificate) {
id allowedPublicKey = nil;
SecCertificateRef allowedCertificate;
SecCertificateRef allowedCertificates[1];
CFArrayRef tempCertificates = nil;
SecPolicyRef policy = nil;
SecTrustRef allowedTrust = nil;
SecTrustResultType result;
//將二進(jìn)制證書轉(zhuǎn)化成`SecCertificateRef`
allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate);
//如果allowedCertificate為空眼滤,則執(zhí)行標(biāo)記_out后邊的代碼
//__Require_Quiet&&_out和if&&else的意思差不多、好處是可以很多入口历涝、然后統(tǒng)一出口
__Require_Quiet(allowedCertificate != NULL, _out);
//給allowedCertificates賦值
allowedCertificates[0] = allowedCertificate;
//新建CFArra: tempCertificates
tempCertificates = CFArrayCreate(NULL, (const void **)allowedCertificates, 1, NULL);
//新建policy為X.509
policy = SecPolicyCreateBasicX509();
//創(chuàng)建SecTrustRef(`&allowedTrust`)對(duì)象诅需。如果出錯(cuò)就跳到_out標(biāo)記處
__Require_noErr_Quiet(SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust), _out);
//校驗(yàn)證書。這個(gè)不是異步的荧库。如果出錯(cuò)也會(huì)調(diào)到_out標(biāo)記處
__Require_noErr_Quiet(SecTrustEvaluate(allowedTrust, &result), _out);
//在SecTrustRef對(duì)象中取出公鑰
allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust);
_out:
//釋放使用過(guò)的對(duì)象
if (allowedTrust) {
CFRelease(allowedTrust);
}
if (policy) {
CFRelease(policy);
}
if (tempCertificates) {
CFRelease(tempCertificates);
}
if (allowedCertificate) {
CFRelease(allowedCertificate);
}
return allowedPublicKey;
}
具體代碼沒(méi)啥可深究的堰塌、有需要的時(shí)候再去查查資料就行了
但有一點(diǎn)很有意思__Require_Quiet
和__Require_noErr_Quiet
作用其實(shí)和if-esle
差不多、但是可以從多個(gè)入口跳到統(tǒng)一的出口分衫、相關(guān)函數(shù)__Require_XXX
基本都是這個(gè)意思场刑。寫了幾個(gè)小方法、想看的自己可以copy運(yùn)行一下
#import <AssertMacros.h>
//斷言為假則會(huì)執(zhí)行一下第三個(gè)action蚪战、拋出異常牵现、并且跳到_out
__Require_Action(1, _out, NSLog(@"直接跳"));
//斷言為真則往下、否則跳到_out
__Require_Quiet(1,_out);
NSLog(@"111");
//如果不注釋邀桑、從這里直接就會(huì)跳到out
// __Require_Quiet(0,_out);
// NSLog(@"222");
//如果沒(méi)有錯(cuò)誤瞎疼、也就是NO、繼續(xù)執(zhí)行
__Require_noErr(NO, _out);
NSLog(@"333");
//如果有錯(cuò)誤壁畸、也就是YES贼急、跳到_out、并且拋出異常定位
__Require_noErr(YES, _out);
NSLog(@"444");
_out:
NSLog(@"end");
2018-05-17 14:18:12.656703+0800 AFNetWorkingDemo[4046:313255] 111
2018-05-17 14:18:12.656944+0800 AFNetWorkingDemo[4046:313255] 333
AssertMacros: YES == 0 , file: /Users/kiritoSong/Desktop/博客/KTAFNetWorkingDemo/AFNetWorkingDemo/AFNetWorkingDemo/ViewController.m, line: 39, value: 1
2018-05-17 14:18:12.657097+0800 AFNetWorkingDemo[4046:313255] end
-
將公鑰轉(zhuǎn)化成NSData
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;
}
-
比對(duì)兩個(gè)公鑰是否相同
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
}
-
返回服務(wù)器是否可以被信任
static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {
BOOL isValid = NO;
SecTrustResultType result;
//校驗(yàn)證書
__Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out);
//kSecTrustResultUnspecified:由非用戶證書校驗(yàn)通過(guò)
//kSecTrustResultProceed:由用戶證書校驗(yàn)通過(guò)
isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
_out:
return isValid;
}
-
取出所有服務(wù)器返回的證書
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];
}
SecTrustRef
:對(duì)象通過(guò)NSURLCredential
傳遞進(jìn)來(lái)的challenge.protectionSpace.serverTrust
也就是在外面通過(guò)- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(nullable NSString*)domain;
函數(shù)傳遞進(jìn)來(lái)供我們校驗(yàn)的證書
-
取出服務(wù)器返回的所有證書中的公鑰
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];
}
-
AFSecurityPolicy對(duì)象方法
@interface AFSecurityPolicy()
@property (readwrite, nonatomic, assign) AFSSLPinningMode SSLPinningMode;
@property (readwrite, nonatomic, strong) NSSet *pinnedPublicKeys;
@end
@implementation AFSecurityPolicy
//取出某個(gè)bundle下所有的證書
+ (NSSet *)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];
}
/**
取當(dāng)前包內(nèi)所有的證書
*/
+ (NSSet *)defaultPinnedCertificates {
static NSSet *_defaultPinnedCertificates = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//根據(jù)當(dāng)前類取出所在包位置
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
_defaultPinnedCertificates = [self certificatesInBundle:bundle];
});
return _defaultPinnedCertificates;
}
#pragma mark <# 工廠方法 #>
+ (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 *)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;
}
//將證書的合集轉(zhuǎn)化成公鑰的合集并且賦值給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];
} else {
self.pinnedPublicKeys = nil;
}
}
-
核心方法
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain
{
//驗(yàn)證不通過(guò)
//host存在 && 允許使用過(guò)期證書(通常都是NO) && 驗(yàn)證域名 && (無(wú)條件信任服務(wù)器證書 || 沒(méi)有證書)
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;
}
//證書數(shù)組
NSMutableArray *policies = [NSMutableArray array];
if (self.validatesDomainName) {
//驗(yàn)證域名
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
//不驗(yàn)證域名
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}
//設(shè)置需要驗(yàn)證的策略
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
if (self.SSLPinningMode == AFSSLPinningModeNone) {
//無(wú)條件信任服務(wù)器的證書
//允許使用過(guò)期或無(wú)效證書 || 服務(wù)器返回的證書可以信任 則返回YES否則NO
return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
} else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
//服務(wù)器返回的證書不通過(guò) && 不允許使用過(guò)期或無(wú)效證書 則不通過(guò)
return NO;
}
/*
代碼走到這里瓤摧、有兩個(gè)條件
1、驗(yàn)證策略并不是無(wú)條件信任服務(wù)器的證書
2玉吁、服務(wù)器證書通過(guò)了信任并且不允許使用過(guò)期或無(wú)效的證書
也就是說(shuō)證書沒(méi)問(wèn)題照弥、但是需要進(jìn)一步驗(yàn)證(公鑰或者本地證書)
*/
switch (self.SSLPinningMode) {
case AFSSLPinningModeNone:
default:
return NO;
case AFSSLPinningModeCertificate: {
//全部檢查
NSMutableArray *pinnedCertificates = [NSMutableArray array];
for (NSData *certificateData in self.pinnedCertificates) {
//將本地的二進(jìn)制證書轉(zhuǎn)化成SecCertificateRef證書并且加入數(shù)組
[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
}
//把本地的證書設(shè)為根證書、即服務(wù)器應(yīng)該信任的證書
SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);
//看看能否被信任
if (!AFServerTrustIsValid(serverTrust)) {
return NO;
}
// 取出所有服務(wù)器返回的證書
NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
//遍歷看看本地證書是否和服務(wù)器證書相同
for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
return YES;
}
}
return NO;
}
case AFSSLPinningModePublicKey: {
NSUInteger trustedPublicKeyCount = 0;
//取出所有服務(wù)器返回證書的公鑰
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;
}
}
}
//如果有相同的公鑰就通過(guò)
return trustedPublicKeyCount > 0;
}
}
return NO;
}
API注釋Demo
把注釋的源碼放在了github上进副、有興趣可以自取这揣。
GitHub
最后
本文主要是自己的學(xué)習(xí)與總結(jié)悔常。如果文內(nèi)存在紕漏、萬(wàn)望留言斧正给赞。如果不吝賜教小弟更加感謝机打。