forked from draveness/analyze
-
Notifications
You must be signed in to change notification settings - Fork 0
/
验证 HTTPS 请求的证书(五).md
384 lines (287 loc) · 15.8 KB
/
验证 HTTPS 请求的证书(五).md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
# 验证 HTTPS 请求的证书(五)
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;
}
```
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/contents/AFNetworking/AFNetworking%20概述(一).md)
+ [AFNetworking 的核心 AFURLSessionManager(二)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/AFNetworking/AFNetworking%20的核心%20AFURLSessionManager(二).md)
+ [处理请求和响应 AFURLSerialization(三)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/AFNetworking/处理请求和响应%20AFURLSerialization(三).md)
+ [AFNetworkReachabilityManager 监控网络状态(四)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/AFNetworking/AFNetworkReachabilityManager%20监控网络状态(四).md)
+ [验证 HTTPS 请求的证书(五)](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/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)