AFNetWorking 源码学习笔记 ☞ Security
一、前言
本文是 AFNetWorking 源码学习笔记的第三篇,本篇将介绍第二部分 -- Security 目录下的类,其实只有一个就是 AFSecurityPolicy。
AFNetWorking-Security.png二、正文
依照惯例,先从声明文件开始,打开 AFSecurityPolicy.h 文件,我们发现内容非常少,只有 4 个属性和 5 个方法。
首先来看看这几个属性,各自的作用见代码注释。
// 根据 SSL 证书验证 server trust 的标准,默认为不验证。
@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode;
// 根据 SSL pinning mode 验证 server trust 的证书
@property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates;
// 是否允许无效或已经过期的 SSL 证书。
@property (nonatomic, assign) BOOL allowInvalidCertificates;
// 是否验证证书中的域名,默认为 YES。
@property (nonatomic, assign) BOOL validatesDomainName;
然后再来看看它提供的 5 个代理方法,
+ (NSSet <NSData *> *)certificatesInBundle:(NSBundle *)bundle;
此类方法的作用是获取 Bundle 中的所有证书文件(xxx.cer)存放在 set 里边。
+ (instancetype)defaultPolicy;
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode;
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet *)pinnedCertificates;
这 3 个方法都在创建 AFSecurityPolicy 对象,第一个默认将属性 SSLPinningMode 设置为 AFSSLPinningModeNone,即不做验证;第二个可以指定 SSLPinningMode,最后一个还可以指定 pinnedCertificates。
下边这个实例方法负责基于当前安全策略评估传入的 serverTrust 是否可以接受,也就是我们在上一篇中接受认证挑战时掉那个的方法。
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(nullable NSString *)domain;
接下来,我们看看 .m 文件,这里就只介绍其中的两个主要方法实现。第一个是 - (void)setPinnedCertificates:(NSSet *)pinnedCertificates
,这里不仅给 _pinnedCertificates 赋值,而且取出了所有证书中的公钥(public key),存放到集合 self.pinnedPublicKeys 里边。
- (void)setPinnedCertificates:(NSSet *)pinnedCertificates {
// 1.给 _pinnedCertificates 赋值
_pinnedCertificates = pinnedCertificates;
// 2.给 self.pinnedPublicKeys 赋值,值是从 Certificates 中提取的 publicKeys
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;
}
}
提取 Certificate 中的 public key 时,利用了 AFPublicKeyForCertificate()
这个静态函数,下面来看看它的具体实现。
static id AFPublicKeyForCertificate(NSData *certificate) {
// 1.初始化会用到的局部变量
id allowedPublicKey = nil;
SecCertificateRef allowedCertificate;
SecPolicyRef policy = nil;
SecTrustRef allowedTrust = nil;
SecTrustResultType result;
// 2.提取公钥
// ① 根据传入的二进制证书数据生成一个 SecCertificateRef 类型的证书,并验证结果
allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate);
__Require_Quiet(allowedCertificate != NULL, _out);
// ② 生成一个默认 X.509 policy 的实例
policy = SecPolicyCreateBasicX509();
// ③ 基于证书 (allowedCertificate) 和安全策略 (policy) 创建一个信任管理对象 (a trust management object)
__Require_noErr_Quiet(SecTrustCreateWithCertificates(allowedCertificate, policy, &allowedTrust), _out);
// ④ 利用步骤 ③ 创建的 allowedTrust 校验证书,并将结果写入 result,不过后边好像没有用到它
__Require_noErr_Quiet(SecTrustEvaluate(allowedTrust, &result), _out);
// ⑤ 验证通过后,从 allowedTrust 中获取公钥
allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust);
_out:
// 3.释放内存
if (allowedTrust) {
CFRelease(allowedTrust);
}
if (policy) {
CFRelease(policy);
}
if (allowedCertificate) {
CFRelease(allowedCertificate);
}
return allowedPublicKey;
}
从上边的代码中我们发现,重点在第 2 步获取公钥的代码,具体内容建代码注释,这里就不啰嗦了。其中用到了系统提供的 2 个宏:__Require_Quiet
和 __Require_noErr_Quiet
,下边来看看他们的定义:
#ifndef __Require_Quiet
#define __Require_Quiet(assertion, exceptionLabel) \
do \
{ \
if ( __builtin_expect(!(assertion), 0) ) \
{ \
goto exceptionLabel; \
} \
} while ( 0 )
#endif
#ifndef __Require_noErr_Quiet
#define __Require_noErr_Quiet(errorCode, exceptionLabel) \
do \
{ \
if ( __builtin_expect(0 != (errorCode), 0) ) \
{ \
goto exceptionLabel; \
} \
} while ( 0 )
#endif
从定义可以看出,对于 __Require_Quiet,当入参 assertion 为空的时候,会跳转到 exceptionLabel 所在位置去继续执行;而 __Require_noErr_Quiet,是当入参 errorCode != 0 即出错的时候会跳转至 exceptionLabel 所在行继续执行。
核心方法
下边我们来看看 上一篇 提到过的方法 evaluateServerTrust: forDomain:
,该方法是 AFNetWorking 自己提前做的一次 HTTPS 认证,用于验证服务器是否合法,如果验证不通过就不再继续调用系统方法验证了。
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain
{
// 1.过滤
// 域名存在 && 允许自签名证书 && 需要验证域名 && (要求不做验证 || 没有证书)
if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
NSLog(@"In order to validate a domain name for self signed certificates, you MUST use pinning.");
return NO;
}
// 2.准备安全策略,并将其设置到 serverTrust 里边,以备后边验证使用
NSMutableArray *policies = [NSMutableArray array];
if (self.validatesDomainName) {
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}
// Sets the policies to use in an evaluation.
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
// 3.再次过滤
if (self.SSLPinningMode == AFSSLPinningModeNone) {
// 不验证的情况下
// 如果允许自签名证书或者 serverTrust 验证成功时,返回 YES
return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
} else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
// 如果 serverTrust 验证失败 && 不允许自签名证书,则返回 NO
return NO;
}
// 4.验证
switch (self.SSLPinningMode) {
case AFSSLPinningModeNone:
/**
* 1.不验证
*
* 但 紧上边的那个 if-else 语句已经处理了 AFSSLPinningModeNone 的情况,这里还做这个判断有意义吗?难道是是为了看起来完整。
*/
default:
return NO;
case AFSSLPinningModeCertificate: {
/**
* 2.验证证书
*
* 从serverTrust中去获取证书链,然后和我们一开始初始化设置的证书集合self.pinnedCertificates去匹配,如果有一对能匹配成功的,就返回YES,否则NO。
*/
NSMutableArray *pinnedCertificates = [NSMutableArray array];
for (NSData *certificateData in self.pinnedCertificates) {
[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
}
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: {
/**
* 3.验证公钥
*
* 从serverTrust,获取证书链每一个证书的公钥,放到数组中。和我们的self.pinnedPublicKeys,去配对,如果有一个相同的,就返回YES,否则NO。
*/
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;
}
代码稍微有点长,不过主要做了这些事:
- 过滤一些特殊情况:域名存在 && 允许自签名证书 && 需要验证域名 && (要求不做验证 || 没有证书);
- 准备安全策略,并将其设置到 serverTrust 里边,以备后边验证使用;
- 再次过滤:不验证、serverTrust 验证失败 && 不允许自签名证书 的情况;
- 开始验证,根据 self.SSLPinningMode 分成了几种情况分别验证:
- 1.不验证
- 2.验证证书
- 3.验证公钥
这一步的验证中又用到了几个静态函数,下边来简单看一下。
- 验证serverTrust 是否有效,调用了系统方法 SecTrustEvaluate() 进行校验。
static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {
BOOL isValid = NO;
SecTrustResultType result;
__Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out);
// 只有当 "用户未指定验证的策略,即使用系统提供的默认策略" 或者 "要求永远信任" 的时候,才会返回 YES,其他时候都是 NO。
isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
_out:
return isValid;
}
- 取出 serverTrust 里边的所有证书,将其二进制文件组成的数组返回。
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];
}
- 取出 serverTrust 里边的所有证书,然后遍历证书取出其中的 public key,将公钥组成的数组返回。
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 中的主要内容,其他部分本文暂不做过多讨论,以后有时间再更新吧。