AFSecurityPolicy
AFSSLPinningMode
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
AFSSLPinningModeNone,
AFSSLPinningModePublicKey,
AFSSLPinningModeCertificate,
};
设置了3中验证服务器是否受信任的方式
- AFSSLPinningModeNone: 默认的认证方式,只会在系统的信任证书列表中对服务器返回的证书进行验证
- AFSSLPinningModePublicKey:需要客户端预先保存服务器的证书
- AFSSLPinningModeCertificate: 需要客户端事先保存服务器端发送的证书,但是只会验证证书中的公钥是否正确
AFSecurityPolicy初始化
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet *)pinnedCertificates {
AFSecurityPolicy *securityPolicy = [[self alloc] init];
securityPolicy.SSLPinningMode = pinningMode;
[securityPolicy setPinnedCertificates:pinnedCertificates];
return securityPolicy;
}
- (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;
}
}
初始化Policy
在调用PinnedCertificates
的setter方法时,调用了AFPublicKeyForCertificate
C函数,对证书进行操作获取公钥,取出全部的公钥保存到pinnedPublicKeys
属性中
操作 SecTrustRef
对SecTrustRef
的操作都是C的API,定义在Security
模块中
static id AFPublicKeyForCertificate(NSData *certificate) {
//初始化临时变量
id allowedPublicKey = nil;
SecCertificateRef allowedCertificate;
SecPolicyRef policy = nil;
SecTrustRef allowedTrust = nil;
SecTrustResultType result;
//通过`DER`表示的数据生成一个`secCertificateRef`
allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate);
//判断返回值是否为空
__Require_Quiet(allowedCertificate != NULL, _out);
//创建一个默认的符合 X509 标准的 SecPolicyRef
policy = SecPolicyCreateBasicX509();
//通过默认的SecPolicyRef和证书创建一个SecTrustRef用于信任评估,对该对象进行信任评估,确认生成的 SecTrustRef 是值得信任的
__Require_noErr_Quiet(SecTrustCreateWithCertificates(allowedCertificate, policy, &allowedTrust), _out);
//确认生成的对象是值得信任的
__Require_noErr_Quiet(SecTrustEvaluate(allowedTrust, &result), _out);
//获取公钥
allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust);
_out:
//释放指针
if (allowedTrust) {
CFRelease(allowedTrust);
}
if (policy) {
CFRelease(policy);
}
if (allowedCertificate) {
CFRelease(allowedCertificate);
}
return allowedPublicKey;
}
注意
每个SecTrustRef对象都包含多个
SecCertificateRef
和SecPolicyRef
。其中SecCertificateRef
可以使用DER进行表示,并且其中存储着公钥信息
除此之外 还有操作还有AFCertificateTrustChainForServerTrust
和AFPublicKeyTrustChainForServerTrust
函数
但是调用了几乎相同的API
SecTrustGetCertificateAtIndex
获取SecTrustRef中的证书SecCertificateCopyData
从证书或者DER中表示的数据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];
}
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 evaluateServerTrust:forDomain:]
来验证服务器端是否受信
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain {
#1: 不能隐式的信任自己签发的证书
#2: 设置policy
#3: 验证证书是否有效
#4: 根据SSLPinningMode对服务端进行验证
return NO;
}
不能隐式的信任自己签发的证书
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;
}因此如果没有提供证书或者不验证证书,并且还设置
allowInvalidCertificates
为真,满足上面所有条件 说明这次验证是不安全的 返回NO设置Policy
NSMutableArray *policies = [NSMutableArray array]; if (self.validatesDomainName) {
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}如果要验证域名的话,就以域名为参数创建一个SecPolicyRef,否则会创建一个符合X509标注的默认
DecPlocyRef
对象验证证书有效性
if (self.SSLPinningMode == AFSSLPinningModeNone) { return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
} else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
return NO;
}- 如果只根据信任列表中证书进行验证,即
self.SSLPinningMode == AFSSLPinningModeNone
。如果允许无效的证书 就返回YES。不允许的话就对服务端进行验证 - 如果服务器信任无效,并且不允许无效证书,就返回NO
- 如果只根据信任列表中证书进行验证,即
根据
SSLPingMode
对服务器信任进行验证AFSSLPinningModeNone
直接返回NOAFSSLPinningModeCertificate
case AFSSLPinningModeCertificate: { 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;
}a. 从
self.pinnedCertificates
中获取DER表示的数据
b. 使用SecTrustSetAnchorCertigicates
为服务器信任设置证书
c. 判断服务器信任的有效性
d. 使用AFCertificateTrustChainForServerTrust
获取服务器信任中的全部DER表示的整数
e. 如果pinnedCertificated中有相同的整数,就会返回YESAFSSLPinningModePublicKey
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;
}与
AFSSLPinningModeCertificate
的不同点在于:- 从服务器信任中获取公钥
pinnedPublicKeys
中的公钥与服务器信任中的公钥相同的属性大于0 返回真
与 AFURLSessionManager 协作
在NSURLSessionDelegate
代理方法中 调用运行这段代码
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
if (self.sessionDidReceiveAuthenticationChallenge) {
disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
} else {
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
if (credential) {
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
}
if (completionHandler) {
completionHandler(disposition, credential);
}
}
NSURLAuthenticationChallenge
表示一个认证的挑战,提供了关于这次认证的全部信息。
其中的protectionSpace
属性,保存了需要认证的保护空间,每个NSURLProtectionSpace
对象都保存了主机地址、端口和认证方法等重要信息
如果保护空间的认证方法为NSURLAuthenticationMethodServerTrust
,那么就会使用在上面提到的方法- [AFSecurityPolicy evaluateServerTrust:forDomain:]
对保护空间的serverTrust
以及域名host
进行认证
根据认证的结果,在completionHandler
中传入不同的disposition
和credential
参数
小结
AFSecurityPolicy 同样也作为一个即插即用的模块,在 AFNetworking 中作为验证 HTTPS 证书是否有效的模块存在