验证 HTTPS 请求的证书(五)
2016-07-29 13:41
225 查看
Blog: [Draveness](http://draveness.me)
<iframe src="http://ghbtns.com/github-btn.html?user=draveness&type=follow&size=large"
height="30" width="240" frameborder="0" scrolling="0" style="width:240px; height: 30px;" allowTransparency="true"></iframe>
自 iOS9
发布之后,由于新特性
[App Transport Security](https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html)
的引入,在默认行为下是不能发送 HTTP
请求的。很多网站都在转用 HTTPS,而
`AFNetworking`
中的
`AFSecurityPolicy`
就是为了阻止中间人攻击,以及其它漏洞的工具。
`AFSecurityPolicy`
主要作用就是验证 HTTPS
请求的证书是否有效,如果 app
中有一些敏感信息或者涉及交易信息,一定要使用 HTTPS
来保证交易或者用户信息的安全。
## AFSSLPinningMode
使用
`AFSecurityPolicy`
时,总共有三种验证服务器是否被信任的方式:
```objectivec
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
AFSSLPinningModeNone,
AFSSLPinningModePublicKey,
AFSSLPinningModeCertificate,
};
```
+ `AFSSLPinningModeNone`
是默认的认证方式,只会在系统的信任的证书列表中对服务端返回的证书进行验证
+ `AFSSLPinningModeCertificate`
需要客户端预先保存服务端的证书
+ `AFSSLPinningModePublicKey`
也需要预先保存服务端发送的证书,但是这里只会验证证书中的公钥是否正确
## 初始化以及设置
在使用 `AFSecurityPolicy`
验证服务端是否受到信任之前,要对其进行初始化,使用初始化方法时,主要目的是设置**验证服务器是否受信任的方式**。
```objectivec
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode {
return [self policyWithPinningMode:pinningMode withPinnedCertificates:[self defaultPinnedCertificates]];
}
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet *)pinnedCertificates {
AFSecurityPolicy *securityPolicy = [[self alloc] init];
securityPolicy.SSLPinningMode = pinningMode;
[securityPolicy setPinnedCertificates:pinnedCertificates];
return securityPolicy;
}
```
这里没有什么地方值得解释的。不过在调用 `pinnedCertificate`
的 setter
方法时,会从全部的证书中**取出公钥**保存到
`pinnedPublicKeys`
属性中。
```objectivec
- (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;
}
}
```
在这里调用了 `AFPublicKeyForCertificate`
对证书进行操作,返回一个公钥。
## 操作 SecTrustRef
对 `serverTrust`
的操作的函数基本上都是 C
的 API,都定义在 `Security`
模块中,先来分析一下在上一节中 `AFPublicKeyForCertificate`
的实现
```objectivec
static id AFPublicKeyForCertificate(NSData *certificate) {
id allowedPublicKey = nil;
SecCertificateRef allowedCertificate;
SecCertificateRef allowedCertificates[1];
CFArrayRef tempCertificates = nil;
SecPolicyRef policy = nil;
SecTrustRef allowedTrust = nil;
SecTrustResultType result;
allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate);
__Require_Quiet(allowedCertificate != NULL, _out);
allowedCertificates[0] = allowedCertificate;
tempCertificates = CFArrayCreate(NULL, (const void **)allowedCertificates, 1, NULL);
policy = SecPolicyCreateBasicX509();
__Require_noErr_Quiet(SecTrustCreateWithCertificates(tempCertificates, 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 (tempCertificates) {
CFRelease(tempCertificates);
}
if (allowedCertificate) {
CFRelease(allowedCertificate);
}
return allowedPublicKey;
18dec
}
```
1. 初始化一坨临时变量
```objectivec
id allowedPublicKey = nil;
SecCertificateRef allowedCertificate;
SecCertificateRef allowedCertificates[1];
CFArrayRef tempCertificates = nil;
SecPolicyRef policy = nil;
SecTrustRef allowedTrust = nil;
SecTrustResultType result;
```
2. 使用 `SecCertificateCreateWithData`
通过 DER
表示的数据生成一个 `SecCertificateRef`,然后判断返回值是否为
`NULL`
```objectivec
allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate);
__Require_Quiet(allowedCertificate != NULL, _out);
```
+ 这里使用了一个非常神奇的宏 `__Require_Quiet`,它会判断
`allowedCertificate != NULL`
是否成立,如果 `allowedCertificate`
为空就会跳到 `_out`
标签处继续执行
```objectivec
#ifndef __Require_Quiet
#define __Require_Quiet(assertion, exceptionLabel) \
do \
{ \
if ( __builtin_expect(!(assertion), 0) ) \
{ \
goto exceptionLabel; \
} \
} while ( 0 )
#endif
```
3. 通过上面的 `allowedCertificate`
创建一个 `CFArray`
```objectivec
allowedCertificates[0] = allowedCertificate;
tempCertificates = CFArrayCreate(NULL, (const void **)allowedCertificates, 1, NULL);
```
+ 下面的 `SecTrustCreateWithCertificates`
只会接收数组作为参数。
4. 创建一个默认的符合 X509
标准的 `SecPolicyRef`,通过默认的
`SecPolicyRef` 和证书创建一个 `SecTrustRef`
用于信任评估,对该对象进行信任评估,确认生成的 `SecTrustRef`
是值得信任的。
```objectivec
policy = SecPolicyCreateBasicX509();
__Require_noErr_Quiet(SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust), _out);
__Require_noErr_Quiet(SecTrustEvaluate(allowedTrust, &result), _out);
```
+
这里使用的 `__Require_noErr_Quiet`
和上面的宏差不多,只是会根据返回值判断是否存在错误。
5. 获取公钥
```objectivec
allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust);
```
+ 这里的 `__bridge_transfer`
会将结果桥接成 `NSObject`
对象,然后将 `SecTrustCopyPublicKey`
返回的指针释放。
6. 释放各种 C
语言指针
```objectivec
if (allowedTrust) {
CFRelease(allowedTrust);
}
if (policy) {
CFRelease(policy);
}
if (tempCertificates) {
CFRelease(tempCertificates);
}
if (allowedCertificate) {
CFRelease(allowedCertificate);
}
```
> 每一个 `SecTrustRef`
的对象都是包含多个 `SecCertificateRef`
和 `SecPolicyRef`。其中 `SecCertificateRef`
可以使用 DER
进行表示,并且其中存储着公钥信息。
对它的操作还有 `AFCertificateTrustChainForServerTrust`
和 `AFPublicKeyTrustChainForServerTrust`
但是它们几乎调用了相同的 API。
```objectivec
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];
}
```
+ `SecTrustGetCertificateAtIndex`
获取 `SecTrustRef`
中的证书
+ `SecCertificateCopyData`
从证书中或者 DER
表示的数据
## 验证服务端是否受信
验证服务端是否守信是通过 `-
[AFSecurityPolicy evaluateServerTrust:forDomain:]` 方法进行的。
```objectivec
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain
{
#1:
不能隐式地信任自己签发的证书
#2: 设置 policy
#3:
验证证书是否有效
#4: 根据 SSLPinningMode
对服务端进行验证
return NO;
}
```
1. 不能隐式地信任自己签发的证书
```objectivec
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;
}
```
> Do not implicitly trust self-signed certificates as anchors (kSecTrustOptionImplicitAnchors).
> Instead, add your own (self-signed) CA certificate to the list of trusted anchors.
+
所以如果没有提供证书或者不验证证书,并且还设置 `allowInvalidCertificates`
为**真**,满足上面的所有条件,说明这次的验证是不安全的,会直接返回
`NO`
2. 设置 policy
```objectivec
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 标准的默认 `SecPolicyRef`
对象
3. 验证证书的有效性
```objectivec
if (self.SSLPinningMode == AFSSLPinningModeNone) {
return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
} else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
return NO;
}
```
+ 如果**只根据信任列表中的证书**进行验证,即
`self.SSLPinningMode == AFSSLPinningModeNone`。如果允许无效的证书的就会直接返回 `YES`。不允许就会对服务端信任进行验证。
+
如果服务器信任无效,并且不允许无效证书,就会返回 `NO`
4. 根据 `SSLPinningMode`
对服务器信任进行验证
```objectivec
switch (self.SSLPinningMode) {
case AFSSLPinningModeNone:
default:
return NO;
case AFSSLPinningModeCertificate: {
...
}
case AFSSLPinningModePublicKey: {
...
}
}
```
+ `AFSSLPinningModeNone`
直接返回 `NO`
+ `AFSSLPinningModeCertificate`
```objectivec
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;
```
1. 从 `self.pinnedCertificates`
中获取 DER
表示的数据
2. 使用 `SecTrustSetAnchorCertificates`
为服务器信任设置证书
3.
判断服务器信任的有效性
4. 使用 `AFCertificateTrustChainForServerTrust`
获取服务器信任中的全部 DER
表示的证书
5. 如果 `pinnedCertificates`
中有相同的证书,就会返回 `YES`
+ `AFSSLPinningModePublicKey`
```objectivec
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;
```
+
这部分的实现和上面的差不多,区别有两点
1.
会从服务器信任中获取公钥
2. `pinnedPublicKeys`
中的公钥与服务器信任中的公钥相同的数量大于 0,就会返回真
## 与 AFURLSessionManager
协作
在代理协议 `- URLSession:didReceiveChallenge:completionHandler:`
或者 `- URLSession:task:didReceiveChallenge:completionHandler:`
代理方法被调用时会运行这段代码
```objectivec
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
disposition = NSURLSessionAuthChallengeUseCredential;
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
} else {
disposition = NSURLSessionAuthChallengeRejectProtectionSpace;
}
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
```
`NSURLAuthenticationChallenge`
表示一个认证的挑战,提供了关于这次认证的全部信息。它有一个非常重要的属性 `protectionSpace`,这里保存了需要认证的保护空间,
每一个 `NSURLProtectionSpace`
对象都保存了主机地址,端口和认证方法等重要信息。
在上面的方法中,如果保护空间中的认证方法为 `NSURLAuthenticationMethodServerTrust`,那么就会使用在上一小节中提到的方法
`- [AFSecurityPolicy evaluateServerTrust:forDomain:]`
对保护空间中的 `serverTrust`
以及域名 `host`
进行认证
根据认证的结果,会在 `completionHandler`
中传入不同的 `disposition`
和 `credential`
参数。
## 小结
+ `AFSecurityPolicy`
同样也作为一个即插即用的模块,在 AFNetworking
中作为验证 HTTPS
证书是否有效的模块存在,在 iOS
对 HTTPS
日渐重视的今天,在我看来,使用 HTTPS
会成为今后 API
开发的标配。
## 相关文章
关于其他 AFNetworking
源代码分析的其他文章:
+ [AFNetworking 概述(一)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/AFNetworking/AFNetworking%20概述(一).md)
+ [AFNetworking 的核心 AFURLSessionManager(二)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/AFNetworking/AFNetworking%20的核心%20AFURLSessionManager(二).md)
+ [处理请求和响应 AFURLSerialization(三)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/AFNetworking/处理请求和响应%20AFURLSerialization(三).md)
+ [AFNetworkReachabilityManager 监控网络状态(四)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/AFNetworking/AFNetworkReachabilityManager%20监控网络状态(四).md)
+ [验证 HTTPS
请求的证书(五)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/AFNetworking/验证%20HTTPS%20请求的证书(五).md)
<iframe src="http://ghbtns.com/github-btn.html?user=draveness&type=follow&size=large" height="30" width="240" frameborder="0" scrolling="0" style="width:240px; height: 30px;" allowTransparency="true"></iframe>
Follow: [@Draveness](https://github.com/Draveness)
<iframe src="http://ghbtns.com/github-btn.html?user=draveness&type=follow&size=large"
height="30" width="240" frameborder="0" scrolling="0" style="width:240px; height: 30px;" allowTransparency="true"></iframe>
自 iOS9
发布之后,由于新特性
[App Transport Security](https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html)
的引入,在默认行为下是不能发送 HTTP
请求的。很多网站都在转用 HTTPS,而
`AFNetworking`
中的
`AFSecurityPolicy`
就是为了阻止中间人攻击,以及其它漏洞的工具。
`AFSecurityPolicy`
主要作用就是验证 HTTPS
请求的证书是否有效,如果 app
中有一些敏感信息或者涉及交易信息,一定要使用 HTTPS
来保证交易或者用户信息的安全。
## AFSSLPinningMode
使用
`AFSecurityPolicy`
时,总共有三种验证服务器是否被信任的方式:
```objectivec
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
AFSSLPinningModeNone,
AFSSLPinningModePublicKey,
AFSSLPinningModeCertificate,
};
```
+ `AFSSLPinningModeNone`
是默认的认证方式,只会在系统的信任的证书列表中对服务端返回的证书进行验证
+ `AFSSLPinningModeCertificate`
需要客户端预先保存服务端的证书
+ `AFSSLPinningModePublicKey`
也需要预先保存服务端发送的证书,但是这里只会验证证书中的公钥是否正确
## 初始化以及设置
在使用 `AFSecurityPolicy`
验证服务端是否受到信任之前,要对其进行初始化,使用初始化方法时,主要目的是设置**验证服务器是否受信任的方式**。
```objectivec
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode {
return [self policyWithPinningMode:pinningMode withPinnedCertificates:[self defaultPinnedCertificates]];
}
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet *)pinnedCertificates {
AFSecurityPolicy *securityPolicy = [[self alloc] init];
securityPolicy.SSLPinningMode = pinningMode;
[securityPolicy setPinnedCertificates:pinnedCertificates];
return securityPolicy;
}
```
这里没有什么地方值得解释的。不过在调用 `pinnedCertificate`
的 setter
方法时,会从全部的证书中**取出公钥**保存到
`pinnedPublicKeys`
属性中。
```objectivec
- (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;
}
}
```
在这里调用了 `AFPublicKeyForCertificate`
对证书进行操作,返回一个公钥。
## 操作 SecTrustRef
对 `serverTrust`
的操作的函数基本上都是 C
的 API,都定义在 `Security`
模块中,先来分析一下在上一节中 `AFPublicKeyForCertificate`
的实现
```objectivec
static id AFPublicKeyForCertificate(NSData *certificate) {
id allowedPublicKey = nil;
SecCertificateRef allowedCertificate;
SecCertificateRef allowedCertificates[1];
CFArrayRef tempCertificates = nil;
SecPolicyRef policy = nil;
SecTrustRef allowedTrust = nil;
SecTrustResultType result;
allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate);
__Require_Quiet(allowedCertificate != NULL, _out);
allowedCertificates[0] = allowedCertificate;
tempCertificates = CFArrayCreate(NULL, (const void **)allowedCertificates, 1, NULL);
policy = SecPolicyCreateBasicX509();
__Require_noErr_Quiet(SecTrustCreateWithCertificates(tempCertificates, 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 (tempCertificates) {
CFRelease(tempCertificates);
}
if (allowedCertificate) {
CFRelease(allowedCertificate);
}
return allowedPublicKey;
18dec
}
```
1. 初始化一坨临时变量
```objectivec
id allowedPublicKey = nil;
SecCertificateRef allowedCertificate;
SecCertificateRef allowedCertificates[1];
CFArrayRef tempCertificates = nil;
SecPolicyRef policy = nil;
SecTrustRef allowedTrust = nil;
SecTrustResultType result;
```
2. 使用 `SecCertificateCreateWithData`
通过 DER
表示的数据生成一个 `SecCertificateRef`,然后判断返回值是否为
`NULL`
```objectivec
allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate);
__Require_Quiet(allowedCertificate != NULL, _out);
```
+ 这里使用了一个非常神奇的宏 `__Require_Quiet`,它会判断
`allowedCertificate != NULL`
是否成立,如果 `allowedCertificate`
为空就会跳到 `_out`
标签处继续执行
```objectivec
#ifndef __Require_Quiet
#define __Require_Quiet(assertion, exceptionLabel) \
do \
{ \
if ( __builtin_expect(!(assertion), 0) ) \
{ \
goto exceptionLabel; \
} \
} while ( 0 )
#endif
```
3. 通过上面的 `allowedCertificate`
创建一个 `CFArray`
```objectivec
allowedCertificates[0] = allowedCertificate;
tempCertificates = CFArrayCreate(NULL, (const void **)allowedCertificates, 1, NULL);
```
+ 下面的 `SecTrustCreateWithCertificates`
只会接收数组作为参数。
4. 创建一个默认的符合 X509
标准的 `SecPolicyRef`,通过默认的
`SecPolicyRef` 和证书创建一个 `SecTrustRef`
用于信任评估,对该对象进行信任评估,确认生成的 `SecTrustRef`
是值得信任的。
```objectivec
policy = SecPolicyCreateBasicX509();
__Require_noErr_Quiet(SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust), _out);
__Require_noErr_Quiet(SecTrustEvaluate(allowedTrust, &result), _out);
```
+
这里使用的 `__Require_noErr_Quiet`
和上面的宏差不多,只是会根据返回值判断是否存在错误。
5. 获取公钥
```objectivec
allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust);
```
+ 这里的 `__bridge_transfer`
会将结果桥接成 `NSObject`
对象,然后将 `SecTrustCopyPublicKey`
返回的指针释放。
6. 释放各种 C
语言指针
```objectivec
if (allowedTrust) {
CFRelease(allowedTrust);
}
if (policy) {
CFRelease(policy);
}
if (tempCertificates) {
CFRelease(tempCertificates);
}
if (allowedCertificate) {
CFRelease(allowedCertificate);
}
```
> 每一个 `SecTrustRef`
的对象都是包含多个 `SecCertificateRef`
和 `SecPolicyRef`。其中 `SecCertificateRef`
可以使用 DER
进行表示,并且其中存储着公钥信息。
对它的操作还有 `AFCertificateTrustChainForServerTrust`
和 `AFPublicKeyTrustChainForServerTrust`
但是它们几乎调用了相同的 API。
```objectivec
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];
}
```
+ `SecTrustGetCertificateAtIndex`
获取 `SecTrustRef`
中的证书
+ `SecCertificateCopyData`
从证书中或者 DER
表示的数据
## 验证服务端是否受信
验证服务端是否守信是通过 `-
[AFSecurityPolicy evaluateServerTrust:forDomain:]` 方法进行的。
```objectivec
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain
{
#1:
不能隐式地信任自己签发的证书
#2: 设置 policy
#3:
验证证书是否有效
#4: 根据 SSLPinningMode
对服务端进行验证
return NO;
}
```
1. 不能隐式地信任自己签发的证书
```objectivec
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;
}
```
> Do not implicitly trust self-signed certificates as anchors (kSecTrustOptionImplicitAnchors).
> Instead, add your own (self-signed) CA certificate to the list of trusted anchors.
+
所以如果没有提供证书或者不验证证书,并且还设置 `allowInvalidCertificates`
为**真**,满足上面的所有条件,说明这次的验证是不安全的,会直接返回
`NO`
2. 设置 policy
```objectivec
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 标准的默认 `SecPolicyRef`
对象
3. 验证证书的有效性
```objectivec
if (self.SSLPinningMode == AFSSLPinningModeNone) {
return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
} else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
return NO;
}
```
+ 如果**只根据信任列表中的证书**进行验证,即
`self.SSLPinningMode == AFSSLPinningModeNone`。如果允许无效的证书的就会直接返回 `YES`。不允许就会对服务端信任进行验证。
+
如果服务器信任无效,并且不允许无效证书,就会返回 `NO`
4. 根据 `SSLPinningMode`
对服务器信任进行验证
```objectivec
switch (self.SSLPinningMode) {
case AFSSLPinningModeNone:
default:
return NO;
case AFSSLPinningModeCertificate: {
...
}
case AFSSLPinningModePublicKey: {
...
}
}
```
+ `AFSSLPinningModeNone`
直接返回 `NO`
+ `AFSSLPinningModeCertificate`
```objectivec
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;
```
1. 从 `self.pinnedCertificates`
中获取 DER
表示的数据
2. 使用 `SecTrustSetAnchorCertificates`
为服务器信任设置证书
3.
判断服务器信任的有效性
4. 使用 `AFCertificateTrustChainForServerTrust`
获取服务器信任中的全部 DER
表示的证书
5. 如果 `pinnedCertificates`
中有相同的证书,就会返回 `YES`
+ `AFSSLPinningModePublicKey`
```objectivec
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;
```
+
这部分的实现和上面的差不多,区别有两点
1.
会从服务器信任中获取公钥
2. `pinnedPublicKeys`
中的公钥与服务器信任中的公钥相同的数量大于 0,就会返回真
## 与 AFURLSessionManager
协作
在代理协议 `- URLSession:didReceiveChallenge:completionHandler:`
或者 `- URLSession:task:didReceiveChallenge:completionHandler:`
代理方法被调用时会运行这段代码
```objectivec
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
disposition = NSURLSessionAuthChallengeUseCredential;
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
} else {
disposition = NSURLSessionAuthChallengeRejectProtectionSpace;
}
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
```
`NSURLAuthenticationChallenge`
表示一个认证的挑战,提供了关于这次认证的全部信息。它有一个非常重要的属性 `protectionSpace`,这里保存了需要认证的保护空间,
每一个 `NSURLProtectionSpace`
对象都保存了主机地址,端口和认证方法等重要信息。
在上面的方法中,如果保护空间中的认证方法为 `NSURLAuthenticationMethodServerTrust`,那么就会使用在上一小节中提到的方法
`- [AFSecurityPolicy evaluateServerTrust:forDomain:]`
对保护空间中的 `serverTrust`
以及域名 `host`
进行认证
根据认证的结果,会在 `completionHandler`
中传入不同的 `disposition`
和 `credential`
参数。
## 小结
+ `AFSecurityPolicy`
同样也作为一个即插即用的模块,在 AFNetworking
中作为验证 HTTPS
证书是否有效的模块存在,在 iOS
对 HTTPS
日渐重视的今天,在我看来,使用 HTTPS
会成为今后 API
开发的标配。
## 相关文章
关于其他 AFNetworking
源代码分析的其他文章:
+ [AFNetworking 概述(一)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/AFNetworking/AFNetworking%20概述(一).md)
+ [AFNetworking 的核心 AFURLSessionManager(二)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/AFNetworking/AFNetworking%20的核心%20AFURLSessionManager(二).md)
+ [处理请求和响应 AFURLSerialization(三)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/AFNetworking/处理请求和响应%20AFURLSerialization(三).md)
+ [AFNetworkReachabilityManager 监控网络状态(四)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/AFNetworking/AFNetworkReachabilityManager%20监控网络状态(四).md)
+ [验证 HTTPS
请求的证书(五)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/AFNetworking/验证%20HTTPS%20请求的证书(五).md)
<iframe src="http://ghbtns.com/github-btn.html?user=draveness&type=follow&size=large" height="30" width="240" frameborder="0" scrolling="0" style="width:240px; height: 30px;" allowTransparency="true"></iframe>
Follow: [@Draveness](https://github.com/Draveness)
相关文章推荐
- 开启Charles情况下requests 请求https遇到证书验证错误的解决方法
- Https请求忽略证书验证最新实现
- java请求https地址如何绕过证书验证?
- java Android OKHttp HTTPS 请求证书验证 PEM证书(1)
- AFNetworking 3.0 https请求忽略证书验证
- java 请求https接口 证书验证
- [转]在 .NET 中远程请求 https 内容时,发生错误:根据验证过程,远程证书无效
- 在 .NET 中远程请求 https 内容时,发生错误:根据验证过程,远程证书无效。
- java 关于httpclient 请求https (如何绕过证书验证)
- 发送https请求时绕过证书验证
- httpclient忽略证书验证允许https请求
- iOS https请求 忽略证书验证
- 关于httpclient 请求https (如何绕过证书验证)
- 关于httpclient 请求https (如何绕过证书验证)
- 解决httpclient 4.5 https请求跳过证书验证
- 使用自定义证书并忽略验证的HTTPS连接Post请求方式的封装
- curl wget 不验证证书进行https请求【转】
- java androidOKHttp HTTPS 请求证书验证 PFX证书(2)
- java 关于httpclient 请求https (如何绕过证书验证)
- OKHttp HTTPS 请求支持所有Https服务器 证书验证忽略客户端证书,(3)