前言:AFSecurityPolicy基于系统API Security框架
1.AFN框架中实现HTTPS请求的客户端校验是通过AFSecurityPolicy对象实现的
2.Security框架用于保证应用程序所管理之数据的安全。该框架提供的接口可用于管理证书、公钥、私钥以及信任策略。它支持生成加密的安全伪随机数。同时,它也支持对证书和Keychain密钥进行保存,是用户敏感数据的安全仓库。
HTTPS请求首先需要TLS/SSL握手
客户端发出握手请求,请求报文主要包含协议版本号,客户端提供的加密算法,一个随机数random_Client。
服务端接收到请求,保存随机数random_Client,然后发送响应给客户端,包括选择的加密算法、版本、压缩算法、一个随机数random_Server,以及证书链。
客户端接收到信息,将随机数random_Server保存,并且对返回的证书链进行校验,如果检验不通过,终止连接。如果校验通过产生随机数字Pre_master,并用证书中的公钥进行加密,将加密内容发送给服务器。同时客户端根据random_Client、random_Server和Pre_master通过相应算法得到今后双方通信的密钥key。客户端逻辑结束。
服务端接收到公钥加密的信息,通过证书的私钥解密得到随机数字Pre_master,然后根据random_Client、random_Server和Pre_master通过算法得到今后双方通信的密钥key。
握手完毕,客户端和服务端通过生成的密钥key和之前约定的对称加密算法对通信过程的报文数据进行加密。
TLS/SSL握手的关键在于客户端对服务器返回的证书进行验证,比较有名的中间人攻击就是通过伪造证书的方式窃取传输过程中加密的数据。
证书校验
SSL证书是数字证书的一种类型,专门用于HTTPS类型的网络请求,遵循X.509标准生成。SSL证书由CA(Certificate Authority)机构负责颁发,证书的申请流程如下:
申请者提供自己的必要信息(包括身份信息,公钥、私钥等)给CA机构。
CA机构认证申请者的信息。
认证通过后创建新证书,并通过哈希算法得到证书的摘要,用自己证书中的私钥加密摘要,得到新证书的签名。
当TLS/SSL握手时,服务端返回证书链,客户端校验证书的流程如下:
1.验证证书的有效期(是否过期)、身份信息等。
2.验证证书的签名,首先用哈希算法计算证书的摘要1,然后用证书链的上一级证书的公钥解密签名,得到摘要2,然后比较摘要1和摘要2是否相等。
3.验证证书颁发者的合法性,即验证上一级证书的签名,需要用再上一级证书的公钥解密签名,然后和哈希算法计算出的摘要进行比较。递归验证,直到验证根证书,由于根证书没有上级证书,是最上级CA颁发的,是自签名的。需要将根证书加入操作系统中作为信任证书。如果将证书链中某一级证书是被设置成了锚点证书,则被视为根证书。
AFSecurityPolicy
@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode; //校验模式
@property (nonatomic, strong, nullable) NSSet
@property (nonatomic, assign) BOOL allowInvalidCertificates; //是否允许无效证书
@property (nonatomic, assign) BOOL validatesDomainName; //是否验证域名
枚举
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
AFSSLPinningModeNone, //默认验证方式
AFSSLPinningModePublicKey, //比较证书的公钥
AFSSLPinningModeCertificate, //比较证书
};
校验证书的方式有三种:
1.AFSSLPinningModeNone表示按照上文的方式验证证书链
2.AF还提供了SSL Pinning的方式验证,该方式把服务端下发的证书预先保存在APP的bundle中,然后通过比较服务端下发的证书和本地证书是否相同来校验证书。AFSSLPinningModeCertificate采用SSL Pinning的方式,首先验证服务器证书的有效期(是否过期)、身份信息等,然后将该证书和bundle中证书进行比较,是否一致。
3.AFSSLPinningModeCertificate同样采用SSL Pinning的方式,但是不验证证书的有效期等信息,同时只是比较两个证书的公钥是否一致。采用SSL Pinning的方式,本地bundle中导入的证书数据由pinnedCertificates维护。
AFSecurityPolicy还提供了允许无效证书验证通过的开关allowInvalidCertificates,以及是否需要验证证书域名的开关validatesDomainName。下面分析一下AFSecurityPolicy相关方法。
+ (NSSet*)certificatesInBundle:(NSBundle*)bundle; 根据bundle的证书的初始化
+ (NSSet*)defaultPinnedCertificates; 默认证书
+ (instancetype)defaultPolicy; AFSecurityPolicy普通初始化 AFSSLPinningModeNone;
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet*)pinnedCertificates 根据pinnedCertificates证书与AFSSLPinningMode 初始化
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode;根据bundle证书与pinningMode初始化
- (void)setPinnedCertificates:(NSSet*)pinnedCertificates; set方法
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString*)domain
如果允许无效的证书,同时希望验证证书的域名,则需要用SSL Pinning的方式验证,即验证证书的方式不能是AFSSLPinningModeNone,或者SSL Pinng需要本地导入证书,即pinnedCertificates数组不能为空。然后判断域名是否需要验证域名,如果需要,则将域名加入需要验证的对象中
NSMutableArray *policies = [NSMutableArray array];
if (self.validatesDomainName) { //需要验证域名
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)]; //将域名加入验证对象中
} else {
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
if (self.SSLPinningMode == AFSSLPinningModeNone) { //默认验证方式
return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust); //加验证书
} else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
return NO;
}
然后判断验证方式如果是AFSSLPinningModeNone且不允许无效证书,则调用AFServerTrustIsValid方法进行校验
static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {
BOOL isValid = NO;
SecTrustResultType result;
__Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out); //方法验证
isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
_out: //goto语句直接
return isValid;
}
通过系统方法SecTrustEvaluate校验证书,将校验结果存储在result中,同时通过__Require_noErr_Quiet宏来处理该方法返回error的情况:
如果该方法调用过程中失败,即errorCode不为0,则通过goto语句跳转,isValid直接返回NO。如果该方法调用成功,则根据result来判断isValid是否为YES。当值为kSecTrustResultUnspecified或者kSecTrustResultProceed时,验证通过。接下来处理AFSSLPinningModeCertificate的情况
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;
}
NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) { //本地证书数组中是否包含和服务端下发的证书内容一样的证书
if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
return YES; //如果包含,则校验通过
}
}
return NO; //否则不通过
}
因为导入APP Bundle中的证书不是CA颁发的,不受信任,所以调用SecTrustSetAnchorCertificates方法将先将这些证书设置为serverTrust证书链上的锚点证书,类似于将这些证书设置为系统信任的根证书,然后调用AFServerTrustIsValid方法校验serverTrust证书链时,如果遇到锚点证书,则终止验证。然后调用AFCertificateTrustChainForServerTrust方法获取serverTrust的证书链serverCertificates,遍历证书链直到发现本地证书pinnedCertificates中有内容相同的证书,服务端下发的证书在本地认可的证书范围内,校验成功,如果没有则校验失败。
接下来处理AFSSLPinningModePublicKey的方式,
case AFSSLPinningModePublicKey: {
NSUInteger trustedPublicKeyCount = 0;
NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust); //获取serverTrust证书链的公钥
for (id trustChainPublicKey in publicKeys) { //匹配本地的证书公钥和serverTrust的公钥
for (id pinnedPublicKey in self.pinnedPublicKeys) {
if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
trustedPublicKeyCount += 1;
}
}
}
return trustedPublicKeyCount > 0; //匹配成功,校验成功
}
该方法首先获取serverTrust证书链的公钥,然后匹配本地的证书公钥和serverTrust的公钥,本地的公钥通过self.pinnedPublicKeys属性维护,在之前设置本地证书的方法中获得
- (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]; //存放在pinnedPublicKeys属性中
} else {
self.pinnedPublicKeys = nil;
}
}
如果匹配成功,则返回校验成功,否则失败。匹配方法AFSecKeyIsEqualToKey调用isEqual:方法进行判断。
总结
AFN框架的AFSecurityPolicy类为我们实现了HTTPS证书校验的功能,且同时支持三种方式校验证书,开发者可以根据不同情况进行选择,如果是CA颁发的证书,开发者不用做额外逻辑,使用起来十分方便。
参考大神作品 :https://blog.csdn.net/panfeng200866/article/details/69662266