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