AFNetworking主要包含四个部分:1.Reachability
2.Security
3.Serialization
4.NSURLSession
其中前三个部分是彼此独立的模块,互相无依赖,NSURLSession模块依赖于前三部分,现在我们就一个模块一个模块的分别解析,本文首先解析Security网络安全监测部分的源码。作为一个实用主义者,我本人是喜欢直接看代码,看不懂的地方再看解释,所以下面会以代码为主。
AFSecurityPolicy.h源码
#import <Foundation/Foundation.h>
#import <Security/Security.h>
typedef NS_ENUM(NSInteger,AFSSLPinningMode) {
//代表无条件信任服务器的证书,
AFSSLPinningModeNone,
//这个模式同样是证书绑定方式验证证书,需要客户端保留服务器的证书拷贝,只是验证时只验证证书的公钥,不需要验证证书的有效期等问题
AFSSLPinningModePublicKey,
//这个表示用证书绑定方式验证证书,需要客户端保留服务器的证书拷贝,包括证书和公钥两部分,全部进行校验
//跟客户端存储的是否一致
AFSSLPinningModeCertificate,
};
NS_ASSUME_NONNULL_BEGIN
/**
苹果已经封装了HTTPS连接的建立,数据的加密解密过程,我们直接可以访问https网站,但苹果并没有验证证书是否合法,无法避免中间人攻击。要做到真正安全通讯,需要我们手动去验证
服务端返回的证书,AFSecurityPolicy就是用来验证HTTPS请求时证书是否正确。
*/
@interface AFSecurityPolicy : NSObject<NSSecureCoding,NSCopying>
//返回SSL Pinning的类型。默认的是AFSSLPinningModeNone。
@property(nonatomic,assign,readonly) AFSSLPinningMode SSLPinningMode;
//这个属性保存着所有的可用做校验的证书的集合,AFNetworking默认会搜索工程中所有.cer的证书文件
//只要在证书集合中任何一个校验通过,evaluateServerTrust:forDomain: 就会返回true,即通过校验。
@property(nonatomic,strong,nullable) NSSet <NSData *>*pinnedCertificates;
/*
一般来说,每个版本的ios设备中,都会包含一些既有的CA根证书,如果接收到的证书是iOS信任的根证书,那么则为合法证书,否则则为非法证书,
自建证书就是非法的,所以自建证书要想通过验证,需要这个这个属性为YES,默认是NO
*/
@property(nonatomic,assign) BOOL allowInvalidCertificates;
//是否验证证书中的域名domain,,这个可以严格的保证其安全性,默认为YES
@property(nonatomic,assign) BOOL validatesDomainName;
//使用此方法从bundle中获取证书,和+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet <NSData *> *)pinnedCertificates;方法结合使用
+ (NSSet <NSData *> *)certificatesInBundle:(NSBundle *)bundle;
/**
默认的安全策略:AFSSLPinningModeNone
allowInvalidCertificates(不允许使用无效证书)
validatesDomainName(验证域名CN)
不对证书和公钥进行验证
*/
+ (instancetype)defaultPolicy;
//根据指定的pinningMode创建并返回一个安全策略
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode;
//根据指定的pinningMode创建并返回一个安全策略
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet <NSData *> *)pinnedCertificates;
/**
核心方法,当服务器响应需要进行证书验证时,此方法会被调用。然后APP根据之前设置的验证策略来判断验证是否通过
*/
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(nullable NSString *)domain;
@end
NS_ASSUME_NONNULL_END复制代码
AFSecurityPolicy.m源码
#import "AFSecurityPolicy.h"
#import <AssertMacros.h>
#if !TARGET_OS_IOS && !TARGET_OS_WATCH && !TARGET_OS_TV
//从公钥中获取数据
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;
}
#endif
//判断两个公钥是否相同
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 id AFPublicKeyForCertificate(NSData *certificate) {
id allowedPublicKey = nil;
SecCertificateRef allowedCertificate;
SecPolicyRef policy = nil;
SecTrustRef allowedTrust = nil;
SecTrustResultType result;
allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate);
__Require_Quiet(allowedCertificate != NULL, _out);
policy = SecPolicyCreateBasicX509();
__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;
}
//验证服务器证书是否可信
static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {
BOOL isValid = NO;
SecTrustResultType result;
__Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out);
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];
}
//获取服务器返回的证书的所有公钥
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];
}
#pragma mark -
@interface AFSecurityPolicy ()
@property(nonatomic,assign,readwrite) AFSSLPinningMode SSLPinningMode;
//存储本地证书的公钥数组
@property(nonatomic,strong,readwrite) NSSet *pinnedPublicKeys;
@end
@implementation AFSecurityPolicy
+(NSSet<NSData *> *)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];
}
//AFNetworking默认会搜索工程中所有.cer的证书文件,你也可以选择手动添加
+(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;
}
+(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<NSData *> *)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;
}
-(void)setPinnedCertificates:(NSSet<NSData *> *)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;
}
}
#pragma mark -
-(BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain
{
/*
判断矛盾条件:
1.有域名,允许不合法证书,域名是否合法
2.因为要验证域名,所以必须不能是后面两种:AFSSLPinningModeNone或者是本地添加的证书为0
*/
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()];
}
//为serverTrust设置验证策略,即告诉客户端如何验证serverTrust
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
if (self.SSLPinningMode == AFSSLPinningModeNone) {
/*
SSLPinningMode为AFSSLPinningModeNone时,allowInvalidCertificates为YES,则代表服务器任何证书都能验证通过,也就是支持自签名证书,如果不是自签名
则要判断该证书是否可信
*/
return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
} else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
//如果服务器证书不是系统信任证书,且不允许不信任的证书通过则返回NO
return NO;
}
switch (self.SSLPinningMode) {
case AFSSLPinningModeNone:
default:
return NO;
case AFSSLPinningModeCertificate: {
/*
把证书data,用系统api转成 SecCertificateRef 类型的数据,SecCertificateCreateWithData函数对原先的pinnedCertificates做一些处理,保证返回的证书都是DER编码的X.509证书
*/
NSMutableArray *pinnedCertificates = [NSMutableArray array];
for (NSData *certificateData in self.pinnedCertificates) {
[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
}
/*
将pinnedCertificates设置成需要参与验证的Anchor Certificate(锚点证书,通过SecTrustSetAnchorCertificates设置了参与校验锚点证书之后,假如验证的数字证书是这个锚点证书的子节点,即验证的数字证书是由锚点证书对应CA或子CA签发的,或是该证书本身,则信任该证书),具体就是调用SecTrustEvaluate来验证。
//serverTrust是服务器来的验证,有需要被验证的证书。
*/
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: {
/*
公钥验证 AFSSLPinningModePublicKey模式同样是用证书绑定(SSL Pinning)方式验证,客户端要有服务端的证书拷贝,只是验证时只验证证书里的公钥,不验证证书的有效期等信息。只要公钥是正确的,就能保证通信不会被窃听,因为中间人没有私钥,无法解开通过公钥加密的数据。
*/
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;
}
#pragma mark - NSKeyValueObserving
//键值依赖,当本地的证书改变时,证书的公钥也改变了
+ (NSSet *)keyPathsForValuesAffectingPinnedPublicKeys {
return [NSSet setWithObject:@"pinnedCertificates"];
}
#pragma mark - NSSecureCoding--------------------------------------------------------------------------------------------------
//下面的内容是NSSecureCoding和NSCopying协议的内容
+ (BOOL)supportsSecureCoding {
return YES;
}
- (instancetype)initWithCoder:(NSCoder *)decoder {
self = [self init];
if (!self) {
return nil;
}
self.SSLPinningMode = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(SSLPinningMode))] unsignedIntegerValue];
self.allowInvalidCertificates = [decoder decodeBoolForKey:NSStringFromSelector(@selector(allowInvalidCertificates))];
self.validatesDomainName = [decoder decodeBoolForKey:NSStringFromSelector(@selector(validatesDomainName))];
self.pinnedCertificates = [decoder decodeObjectOfClass:[NSArray class] forKey:NSStringFromSelector(@selector(pinnedCertificates))];
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:[NSNumber numberWithUnsignedInteger:self.SSLPinningMode] forKey:NSStringFromSelector(@selector(SSLPinningMode))];
[coder encodeBool:self.allowInvalidCertificates forKey:NSStringFromSelector(@selector(allowInvalidCertificates))];
[coder encodeBool:self.validatesDomainName forKey:NSStringFromSelector(@selector(validatesDomainName))];
[coder encodeObject:self.pinnedCertificates forKey:NSStringFromSelector(@selector(pinnedCertificates))];
}
#pragma mark - NSCopying
- (instancetype)copyWithZone:(NSZone *)zone {
AFSecurityPolicy *securityPolicy = [[[self class] allocWithZone:zone] init];
securityPolicy.SSLPinningMode = self.SSLPinningMode;
securityPolicy.allowInvalidCertificates = self.allowInvalidCertificates;
securityPolicy.validatesDomainName = self.validatesDomainName;
securityPolicy.pinnedCertificates = [self.pinnedCertificates copyWithZone:zone];
return securityPolicy;
}
@end
复制代码
如何使用AFSecurityPolicy这个类进行安全策略设置:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
//allowInvalidCertificates 是否允许无效证书(也就是自建的证书),默认为NO
// 如果是需要验证自建证书,需要设置为YES
securityPolicy.allowInvalidCertificates = YES;
/*
//validatesDomainName 是否需要验证域名,默认为YES;
//假如证书的域名与你请求的域名不一致,需把该项设置为NO;如设成NO的话,即服务器使用其他可信任机构颁发的证书,也可以建立连接,这个非常危险,建议打开。
//置为NO,主要用于这种情况:客户端请求的是子域名,而证书上的是另外一个域名。因为SSL证书上的域名是独立的,假如证书上注册的域名是www.google.com,那么mail.google.com是无法验证通过的;当然,有钱可以注册通配符的域名*.google.com,但这个还是比较贵的。
*/
securityPolicy.validatesDomainName = YES;
return YES;
}复制代码
参考资料如下:
AFNetworking3.0源码解读(5)AFSecurityPolicy
接下来呢打算先写一下关于KVO的知识和HTTPS是如何建立通过加密建立安全连接的知识,源码解析先暂停一下下啦。