此篇文章主要是记录一下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证书链公钥