iOS源码补完计划--AFNetworking(三)
2018-05-17 本文已影响161人
kirito_song
目录
- 前言
- AFSecurityPolicy.h
- 对服务器证书的验证策略
- 属性
- 获取证书
- 自定义安全策略
- 核心方法
- AFSecurityPolicy.m
- SecTrustRef
- 从证书中提取公钥
- 将公钥转化成NSData
- 比对两个公钥是否相同
- 返回服务器是否可以被信任
- 取出所有服务器返回的证书
- 取出服务器返回的所有证书中的公钥
- AFSecurityPolicy对象方法
- 核心方法
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain
- API注释Demo
- 参考资料
前言
AFNetworking源码第三篇
主要看了看AFSecurityPolicy的内容
负责网络安全策略(证书)的验证
作为一个辅助模块、代码量和文件都比较少
一行一行读下来就可以了
但是最好把HTTP/HTTPS好好理解一下、这里就先不提了。将来看网络协议的时候好好补一下。
AFN概述:《iOS源码补完计划--AFNetworking 3.1.0源码研读》
AFSecurityPolicy.h
对服务器证书的验证策略
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
AFSSLPinningModeNone,//无条件信任服务器的证书
AFSSLPinningModePublicKey,//对服务器返回的证书中的PublicKey进行验证
AFSSLPinningModeCertificate,//对服务器返回的证书同本地证书全部进行校验
};
属性
/**
SSLPinning 默认 `AFSSLPinningModeNone`
*/
@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode;
/**
本地证书合集
默认、将会从整个工程目录下加载所有(.cer)的证书文件
如果想定制证书、可以使用`certificatesInBundle`来加载证书
然后调用`policyWithPinningMode:withPinnedCertificates`来创建一个新`AFSecurityPolicy`对象用于验证
如果证书合集中任何一个被校验通过、那么`evaluateServerTrust:forDomain:`都将返回true
*/
@property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates;
/**
使用允许无效或过期的证书 默认`NO`
*/
@property (nonatomic, assign) BOOL allowInvalidCertificates;
/**
是否验证域名 默认`YES`
*/
@property (nonatomic, assign) BOOL validatesDomainName;
获取证书
/**
从指定`bundle`中获取证书合集
然后调用`policyWithPinningMode:withPinnedCertificates`来创建一个新`AFSecurityPolicy`对象用于验证
*/
+ (NSSet <NSData *> *)certificatesInBundle:(NSBundle *)bundle;
自定义安全策略
/**
默认的安全策略
1、不允许无效或过期的证书
2、验证域名
3、不对证书和公钥进行验证
*/
+ (instancetype)defaultPolicy;
///---------------------
/// @name Initialization
///---------------------
/**
通过指定的验证策略`AFSSLPinningMode`来创建
*/
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode;
/**
通过指定的验证策略`AFSSLPinningMode`、以及证书合集来创建
*/
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet <NSData *> *)pinnedCertificates;
核心方法
/**
根据具体配置、确定是否接受指定服务器的信任
服务器验证时会返回`NSURLCredential`challenge对象
@param serverTrust 使用challenge.protectionSpace.serverTrust参数即可
@param domain 使用challenge.protectionSpace.host即可
*/
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(nullable NSString *)domain;
AFSecurityPolicy.m
其实整个.m文件也太多可以研究的地方、因为都是固定的方法。你只能这么写~
不过、一行一行看一看。iOS的证书到底是如何验证的、也不错。
-
SecTrustRef
整个验证都是基于SecTrustRef
的、和.cer
文件的关系大概是:
NSData格式的证书
=>SecCertificateRef
=>SecTrustRef
对象
这个SecTrustRef
通过
CFDataRef SecCertificateCopyData(SecCertificateRef certificate)
SecKeyRef SecTrustCopyPublicKey(SecTrustRef trust)
的方式又可以取出证书和公钥可见。
SecTrustRef
就是一个内部至少携带了证书与公钥的结构体。
-
从证书中提取公钥
static id AFPublicKeyForCertificate(NSData *certificate) {
id allowedPublicKey = nil;
SecCertificateRef allowedCertificate;
SecCertificateRef allowedCertificates[1];
CFArrayRef tempCertificates = nil;
SecPolicyRef policy = nil;
SecTrustRef allowedTrust = nil;
SecTrustResultType result;
//将二进制证书转化成`SecCertificateRef`
allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate);
//如果allowedCertificate为空,则执行标记_out后边的代码
//__Require_Quiet&&_out和if&&else的意思差不多、好处是可以很多入口、然后统一出口
__Require_Quiet(allowedCertificate != NULL, _out);
//给allowedCertificates赋值
allowedCertificates[0] = allowedCertificate;
//新建CFArra: tempCertificates
tempCertificates = CFArrayCreate(NULL, (const void **)allowedCertificates, 1, NULL);
//新建policy为X.509
policy = SecPolicyCreateBasicX509();
//创建SecTrustRef(`&allowedTrust`)对象。如果出错就跳到_out标记处
__Require_noErr_Quiet(SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust), _out);
//校验证书。这个不是异步的。如果出错也会调到_out标记处
__Require_noErr_Quiet(SecTrustEvaluate(allowedTrust, &result), _out);
//在SecTrustRef对象中取出公钥
allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust);
_out:
//释放使用过的对象
if (allowedTrust) {
CFRelease(allowedTrust);
}
if (policy) {
CFRelease(policy);
}
if (tempCertificates) {
CFRelease(tempCertificates);
}
if (allowedCertificate) {
CFRelease(allowedCertificate);
}
return allowedPublicKey;
}
具体代码没啥可深究的、有需要的时候再去查查资料就行了
但有一点很有意思__Require_Quiet
和__Require_noErr_Quiet
作用其实和if-esle
差不多、但是可以从多个入口跳到统一的出口、相关函数__Require_XXX
基本都是这个意思。写了几个小方法、想看的自己可以copy运行一下
#import <AssertMacros.h>
//断言为假则会执行一下第三个action、抛出异常、并且跳到_out
__Require_Action(1, _out, NSLog(@"直接跳"));
//断言为真则往下、否则跳到_out
__Require_Quiet(1,_out);
NSLog(@"111");
//如果不注释、从这里直接就会跳到out
// __Require_Quiet(0,_out);
// NSLog(@"222");
//如果没有错误、也就是NO、继续执行
__Require_noErr(NO, _out);
NSLog(@"333");
//如果有错误、也就是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
-
将公钥转化成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;
}
-
比对两个公钥是否相同
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 BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {
BOOL isValid = NO;
SecTrustResultType result;
//校验证书
__Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out);
//kSecTrustResultUnspecified:由非用户证书校验通过
//kSecTrustResultProceed:由用户证书校验通过
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];
}
SecTrustRef
:对象通过NSURLCredential
传递进来的challenge.protectionSpace.serverTrust
也就是在外面通过- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(nullable NSString*)domain;
函数传递进来供我们校验的证书
-
取出服务器返回的所有证书中的公钥
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对象方法
@interface AFSecurityPolicy()
@property (readwrite, nonatomic, assign) AFSSLPinningMode SSLPinningMode;
@property (readwrite, nonatomic, strong) NSSet *pinnedPublicKeys;
@end
@implementation AFSecurityPolicy
//取出某个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];
}
/**
取当前包内所有的证书
*/
+ (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;
}
#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;
}
//将证书的合集转化成公钥的合集并且赋值给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
{
//验证不通过
//host存在 && 允许使用过期证书(通常都是NO) && 验证域名 && (无条件信任服务器证书 || 没有证书)
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()];
}
//设置需要验证的策略
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
if (self.SSLPinningMode == AFSSLPinningModeNone) {
//无条件信任服务器的证书
//允许使用过期或无效证书 || 服务器返回的证书可以信任 则返回YES否则NO
return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
} else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
//服务器返回的证书不通过 && 不允许使用过期或无效证书 则不通过
return NO;
}
/*
代码走到这里、有两个条件
1、验证策略并不是无条件信任服务器的证书
2、服务器证书通过了信任并且不允许使用过期或无效的证书
也就是说证书没问题、但是需要进一步验证(公钥或者本地证书)
*/
switch (self.SSLPinningMode) {
case AFSSLPinningModeNone:
default:
return NO;
case AFSSLPinningModeCertificate: {
//全部检查
NSMutableArray *pinnedCertificates = [NSMutableArray array];
for (NSData *certificateData in self.pinnedCertificates) {
//将本地的二进制证书转化成SecCertificateRef证书并且加入数组
[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;
}
case AFSSLPinningModePublicKey: {
NSUInteger trustedPublicKeyCount = 0;
//取出所有服务器返回证书的公钥
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;
}
API注释Demo
把注释的源码放在了github上、有兴趣可以自取。