AFNetWorking源码之AFSecurityPolicy

Posted by 黄成都 on 2017-04-26
Words 6.2k and Reading Time 25 Minutes
Viewed Times

1 HTTPS以及SSL/TSL

SSL(Secure Sockets Layer, 安全套接字层),因为原先互联网上使用的HTTP协议是明文的,存在很多缺点,比如传输内容会被偷窥和篡改。SSL协议的作用就是在传输层对网络连接进行加密。

到了1999年,SSL 因为应用广泛,已经成为互联网上的事实标准。IETF就在那年把SSL标准化。标准化之后的名称改为 TLS(Transport Layer Security,传输层安全协议)。SSL与TLS可以视作同一个东西的不同阶段。

简单来说,HTTPS = HTTP + SSL/TLS, 也就是HTTP over SSLHTTP over TLS,这是后面加S的由来。

HTTPS和HTTP异同:HTTP和HTTPS使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。HTTP的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比HTTP协议安全。

2 HTTPS的握手

img

1客户端发出请求(ClientHello)

首先,客户端(通常是浏览器)先向服务器发出加密通信的请求,这被叫做ClientHello请求。在这一步,客户端主要向服务器提供以下信息。

  • (1)支持的协议版本,比如TLS1.0版。
  • (2)一个客户端生成的随机数,稍后用于生成”对话密钥”。
  • (3)支持的加密方法,比如RSA公钥加密。
  • (4)支持的压缩方法。

2服务器回应(SeverHello)

服务器收到客户端请求后,向客户端发出回应,这叫做SeverHello。服务器的回应包含以下内容。

  • (1)确认使用的加密通信协议版本,比如TLS1.0版本。如果浏览器与服务器支持的版本不一致,服务器关闭加密通信。
  • (2)一个服务器生成的随机数,稍后用于生成”对话密钥”。
  • (3)确认使用的加密方法,比如RSA公钥加密。
  • (4)服务器证书。

3客户端回应

客户端收到服务器回应以后,首先验证服务器证书。如果证书不是可信机构颁布、或者证书中的域名与实际域名不一致、或者证书已经过期,就会向访问者显示一个警告,由其选择是否还要继续通信。如果证书没有问题,客户端就会从证书中取出服务器的公钥。然后,向服务器发送下面三项信息。

  • (1)一个随机数。该随机数用服务器公钥加密,防止被窃听。
  • (2)编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送。
  • (3)客户端握手结束通知,表示客户端的握手阶段已经结束。这一项同时也是前面发送的所有内容的hash值,用来供服务器校验。

上面第一项的随机数,是整个握手阶段出现的第三个随机数,又称”pre-master key”。有了它以后,客户端和服务器就同时有了三个随机数,接着双方就用事先商定的加密方法,各自生成本次会话所用的同一把”会话密钥”。

4服务器的最后回应

服务器收到客户端的第三个随机数pre-master key之后,计算生成本次会话所用的”会话密钥”。然后,向客户端最后发送下面信息。

  • (1)编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送。
  • (2)服务器握手结束通知,表示服务器的握手阶段已经结束。这一项同时也是前面发送的所有内容的hash值,用来供客户端校验。

至此,整个握手阶段全部结束。接下来,客户端与服务器进入加密通信,就完全是使用普通的HTTP协议,只不过用”会话密钥”加密内容。

3 数字证书

上面握手阶段的第二步服务器给客户端的证书就是数字证书,该证书包含了公钥等信息,一般是由服务器发给客户端,接收方通过验证这个证书是不是由信赖的CA签发,或者与本地的证书相对比,来判断证书是否可信;假如需要双向验证,则服务器和客户端都需要发送数字证书给对方验证。

数字证书是一个电子文档,其中包含了持有者的信息、公钥以及证明该证书有效的数字签名。而数字证书以及相关的公钥管理和验证等技术组成了PKI(公钥基础设施)规范体系。一般来说,数字证书是由数字证书认证机构(Certificate authority,即CA)来负责签发和管理,并承担PKI体系中公钥合法性的检验责任;数字证书的类型有很多,而HTTPS使用的是SSL证书。

怎么来验证数字证书是由CA签发的,而不是第三方伪造的呢? 在回答这个问题前,我们需要先了解CA的组织结构。首先,CA组织结构中,最顶层的就是根CA,根CA下可以授权给多个二级CA,而二级CA又可以授权多个三级CA,所以CA的组织结构是一个树结构。对于SSL证书市场来说,主要被Symantec(旗下有VeriSign和GeoTrust)、Comodo SSL、Go Daddy 和 GlobalSign 瓜分。 了解了CA的组织结构后,来看看数字证书的签发流程:

img

数字证书的签发机构CA,在接收到申请者的资料后进行核对并确定信息的真实有效,然后就会制作一份符合X.509标准的文件。证书中的证书内容包括了持有者信息和公钥等都是由申请者提供的,而数字签名则是CA机构对证书内容进行hash加密后等到的,而这个数字签名就是我们验证证书是否是有可信CA签发的数据。

img

接收端接到一份数字证书Cer1后,对证书的内容做Hash等到H1;然后在签发该证书的机构CA1的数字证书中找到公钥,对证书上数字签名进行解密,得到证书Cer1签名的Hash摘要H2;对比H1和H2,假如相等,则表示证书没有被篡改。但这个时候还是不知道CA是否是合法的,我们看到上图中有CA机构的数字证书,这个证书是公开的,所有人都可以获取到。而这个证书中的数字签名是上一级生成的,所以可以这样一直递归验证下去,直到根CA。根CA是自验证的,即他的数字签名是由自己的私钥来生成的。合法的根CA会被浏览器和操作系统加入到权威信任CA列表中,这样就完成了最终的验证。所以,一定要保护好自己环境(浏览器/操作系统)中根CA信任列表,信任了根CA就表示信任所有根CA下所有子级CA所签发的证书,不要随便添加根CA证书。

4 SSL Pinning

可以理解为证书绑定,是指客户端直接保存服务端的证书,建立https连接时直接对比服务端返回的和客户端保存的两个证书是否一样,一样就表明证书是真的,不再去系统的信任证书机构里寻找验证。这适用于非浏览器应用,因为浏览器跟很多未知服务端打交道,无法把每个服务端的证书都保存到本地,但CS架构的像手机APP事先已经知道要进行通信的服务端,可以直接在客户端保存这个服务端的证书用于校验。

为什么直接对比就能保证证书没问题?如果中间人从客户端取出证书,再伪装成服务端跟其他客户端通信,它发送给客户端的这个证书不就能通过验证吗?确实可以通过验证,但后续的流程走不下去,因为下一步客户端会用证书里的公钥加密,中间人没有这个证书的私钥就解不出内容,也就截获不到数据,这个证书的私钥只有真正的服务端有,中间人伪造证书主要伪造的是公钥。

为什么要用SSL Pinning?正常的验证方式不够吗?如果服务端的证书是从受信任的的CA机构颁发的,验证是没问题的,但CA机构颁发证书比较昂贵,小企业或个人用户可能会选择自己颁发证书,这样就无法通过系统受信任的CA机构列表验证这个证书的真伪了,所以需要SSL Pinning这样的方式去验证。

5 iOS的HTTPS请求

下面我会实现自签名证书(12306)、SSL信任证书(baidu)、系统证书(苹果)三种情况的实现来看他们的区别,百度和12306的证书已经被我下载到我的项目里面了,具体可以去Demo里面看实现过程。

1 自签名证书

我们手动指定securityPolicy认证属性。通过12306证书来实现。

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
//自建证书认证
- (IBAction)buttion1:(id)sender {
NSURL *url = [NSURL URLWithString:@"https://kyfw.12306.cn/otn/leftTicket/init"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// [request setValue:@"text/html" forHTTPHeaderField:@"Accept"];
AFURLSessionManager *manager = [[AFURLSessionManager alloc]initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
//指定安全策略
manager.securityPolicy = [self ticketSecurityPolicy];
//指定返回数据类型,默认是AFJSONResponseSerializer类型,犹豫这里不是JSON类型的返回数据,所以需要手动指定返回类型
AFHTTPResponseSerializer *responseSerializer = [AFHTTPResponseSerializer serializer];
responseSerializer.acceptableContentTypes = [NSSet setWithObject:@"text/html"];
manager.responseSerializer = responseSerializer;
NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
NSLog(@"%@-----%@",[[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding],error);
}];
[dataTask resume];

}
/**
12306的认证证书,他的认证证书是自签名的

@return 返回指定的认证策略
*/
-(AFSecurityPolicy*)ticketSecurityPolicy {
// /先导入证书
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"12306" ofType:@"cer"];//证书的路径
NSData *certData = [NSData dataWithContentsOfFile:cerPath];
NSSet *set = [NSSet setWithObject:certData];

AFSecurityPolicy *securityPolicy;
if (true) {
securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:set];
}else{
// AFSSLPinningModeCertificate 使用证书验证模式。下面这个方法会默认使用项目里面的所有证书
securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
}
// allowInvalidCertificates 是否允许无效证书(也就是自建的证书),默认为NO
// 如果是需要验证自建证书,需要设置为YES
securityPolicy.allowInvalidCertificates = YES;

//validatesDomainName 是否需要验证域名,默认为YES;
//假如证书的域名与你请求的域名不一致,需把该项设置为NO;如设成NO的话,即服务器使用其他可信任机构颁发的证书,也可以建立连接,这个非常危险,建议打开。
//置为NO,主要用于这种情况:客户端请求的是子域名,而证书上的是另外一个域名。因为SSL证书上的域名是独立的,假如证书上注册的域名是www.google.com,那么mail.google.com是无法验证通过的;当然,有钱可以注册通配符的域名*.google.com,但这个还是比较贵的。
//如置为NO,建议自己添加对应域名的校验逻辑。
securityPolicy.validatesDomainName = NO;

return securityPolicy;
}

2 SSL信任证书

我们手动指定securityPolicy认证属性。通过百度证书来实现。

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
//认证证书认证
- (IBAction)button2:(id)sender {
NSURL *url = [NSURL URLWithString:@"https://www.baidu.com"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
//[request setValue:@"text/html" forHTTPHeaderField:@"Accept"];
AFURLSessionManager *manager = [[AFURLSessionManager alloc]initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
//指定安全策略
manager.securityPolicy = [self baiduSecurityPolicy];
//指定返回数据类型,默认是AFJSONResponseSerializer类型,犹豫这里不是JSON类型的返回数据,所以需要手动指定返回类型
AFHTTPResponseSerializer *responseSerializer = [AFHTTPResponseSerializer serializer];
responseSerializer.acceptableContentTypes = [NSSet setWithObject:@"text/html"];
manager.responseSerializer = responseSerializer;
NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
NSLog(@"%@-----%@",[[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding],error);
}];
[dataTask resume];
}

/**
百度的的认证证书,他的认证证书是花钱买的,也就是不是自签名的证书。这种证书,如果我们要手动指定,pinmode只能是`AFSSLPinningModeNone`

@return 返回指定的认证策略
*/
-(AFSecurityPolicy*)baiduSecurityPolicy {
// /先导入证书
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"baidu" ofType:@"cer"];//证书的路径
NSData *certData = [NSData dataWithContentsOfFile:cerPath];
NSSet *set = [NSSet setWithObject:certData];

AFSecurityPolicy *securityPolicy;
if (true) {
//这里只能用AFSSLPinningModeNone才能成功,而且我系统的证书列表里面已经有百度的证书了
securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone withPinnedCertificates:set];
}else{
// AFSSLPinningModeCertificate 使用证书验证模式。下面这个方法会默认使用项目里面的所有证书
securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
}
// allowInvalidCertificates 是否允许无效证书(也就是自建的证书),默认为NO
// 如果是需要验证自建证书,需要设置为YES
securityPolicy.allowInvalidCertificates = NO;

//validatesDomainName 是否需要验证域名,默认为YES;
//假如证书的域名与你请求的域名不一致,需把该项设置为NO;如设成NO的话,即服务器使用其他可信任机构颁发的证书,也可以建立连接,这个非常危险,建议打开。
//置为NO,主要用于这种情况:客户端请求的是子域名,而证书上的是另外一个域名。因为SSL证书上的域名是独立的,假如证书上注册的域名是www.google.com,那么mail.google.com是无法验证通过的;当然,有钱可以注册通配符的域名*.google.com,但这个还是比较贵的。
//如置为NO,建议自己添加对应域名的校验逻辑。
securityPolicy.validatesDomainName = YES;

return securityPolicy;
}

3 SSL证书AFN默认处理

这里我们不做任何额外的处理,直接使用AFN的默认证书处理机制。通过AFURLSessionManagersecurityPolicy默认实现。它会和存在系统中的做对比来验证证书。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//系统证书认证
- (IBAction)button3:(id)sender {
NSURL *url = [NSURL URLWithString:@"https://www.apple.com/"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
AFURLSessionManager *manager = [[AFURLSessionManager alloc]initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
//指定返回数据类型,默认是AFJSONResponseSerializer类型,犹豫这里不是JSON类型的返回数据,所以需要手动指定返回类型
AFHTTPResponseSerializer *responseSerializer = [AFHTTPResponseSerializer serializer];
responseSerializer.acceptableContentTypes = [NSSet setWithObject:@"text/html"];
manager.responseSerializer = responseSerializer;
NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
NSLog(@"%@-----%@",[[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding],error);
}];
[dataTask resume];
}

6 AFSecurityPolicy源码解析

AFSecurityPolicy分三种验证模式

  • AFSSLPinningModeNone:
    这个模式表示不做SSL pinning,只跟浏览器一样在系统的信任机构列表里验证服务端返回的证书。若证书是信任机构签发的就会通过,若是自己服务器生成的证书,这里是不会通过的。
  • AFSSLPinningModeCertificate:
    这个模式表示用证书绑定方式验证证书,需要客户端保存有服务端的证书拷贝,这里验证分两步,第一步验证证书的域名/有效期等信息,第二步是对比服务端返回的证书跟客户端返回的是否一致。这里还没弄明白第一步的验证是怎么进行的,代码上跟去系统信任机构列表里验证一样调用了SecTrustEvaluate,只是这里的列表换成了客户端保存的那些证书列表。若要验证这个,是否应该把服务端证书的颁发机构根证书也放到客户端里?
  • AFSSLPinningModePublicKey:
    这个模式同样是用证书绑定方式验证,客户端要有服务端的证书拷贝,只是验证时只验证证书里的公钥,不验证证书的有效期等信息。只要公钥是正确的,就能保证通信不会被窃听,因为中间人没有私钥,无法解开通过公钥加密的数据。

SecTrustRef

这是一个需要验证的信任对象,包含待验证的证书和支持的验证方法等。

SecTrustResultType

表示验证结果。其中 kSecTrustResultProceed表示serverTrust验证成功,且该验证得到了用户认可(例如在弹出的是否信任的alert框中选择always trust)。 kSecTrustResultUnspecified表示 serverTrust验证成功,此证书也被暗中信任了,但是用户并没有显示地决定信任该证书。 两者取其一就可以认为对serverTrust验证成功。

SecTrustEvaluate

证书校验函数,在函数的内部递归地从叶节点证书到根证书验证。需要验证证书本身的合法性(验证签名完整性,验证证书有效期等);验证证书颁发者的合法性(查找颁发者的证书并检查其合法性,这个过程是递归的).而递归的终止条件是证书验证过程中遇到了锚点证书(锚点证书:嵌入到操作系统中的根证书,这个根证书是权威证书颁发机构颁发的自签名证书)。

AFSecurityPolicy的源码细节如下:

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
/**
证书的验证类型

- AFSSLPinningModeNone: 不使用`pinned certificates`来验证证书
- AFSSLPinningModePublicKey: 使用`pinned certificates`来验证证书的公钥
- AFSSLPinningModeCertificate: 使用`pinned certificates`来验证整个证书
*/
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
AFSSLPinningModeNone,
AFSSLPinningModePublicKey,
AFSSLPinningModeCertificate,
};

/**
获取指定证书的公钥

@param certificate 证书数据
@return 公钥
*/
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);
//获取X.509的认证策略
policy = SecPolicyCreateBasicX509();
//获取allowedTrust对象的值
__Require_noErr_Quiet(SecTrustCreateWithCertificates(allowedCertificate, policy, &allowedTrust), _out);
__Require_noErr_Quiet(SecTrustEvaluate(allowedTrust, &result), _out);
//根据allowedTrust获取对应的公钥
allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust);
//C++的gumpto跳转,当前面的操作出错以后,直接跳入_out执行
_out:
if (allowedTrust) {
CFRelease(allowedTrust);
}
if (policy) {
CFRelease(policy);
}
if (allowedCertificate) {
CFRelease(allowedCertificate);
}
//返回公钥
return allowedPublicKey;
}

/**
在指定的证书和认证策略下,验证SecTrustRef对象是否是受信任的、合法的。

@param serverTrust SecTrustRef对象
@return 结果
*/
static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {
BOOL isValid = NO;
SecTrustResultType result;
//获取serverTrust的认证结果,调用`SecTrustEvaluate`表示通过系统的证书来比较认证
__Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out);
isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);

_out:
return isValid;
}

/**
根据`serverTrust`获取认证的证书链

@param serverTrust serverTrust对象
@return 认证证书链
*/
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];
}

/**
获取serverTrust对象的认证链的公钥数组

@param serverTrust serverTrust对象
@return 公钥数组
*/
static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) {
//X.509标准的安全策略
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;
//通过一个证书、认证策略新建一个SecTrustRef对象
__Require_noErr_Quiet(SecTrustCreateWithCertificates(certificates, policy, &trust), _out);

SecTrustResultType result;
//验证SecTrustRef对象是否成功
__Require_noErr_Quiet(SecTrustEvaluate(trust, &result), _out);
//把SecTrustRef对应的公钥加入数组中
[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 (readwrite, nonatomic, assign) AFSSLPinningMode SSLPinningMode;
//公钥集合
@property (readwrite, nonatomic, strong) NSSet *pinnedPublicKeys;
@end

@implementation AFSecurityPolicy
/**
从MainBundle中获取所有证书

@param bundle 返回包含在bundle中的证书集合。如果AFNetworking使用的是静态库,我们必须通过这个方法来加载证书。并且通过`policyWithPinningMode:withPinnedCertificates`方法来指定认证类型。
@return 返回bundle里面的证书
*/
+ (NSSet *)certificatesInBundle:(NSBundle *)bundle {
//获取项目里的所有.cer证书
NSArray *paths = [bundle pathsForResourcesOfType:@"cer" inDirectory:@"."];
NSMutableSet *certificates = [NSMutableSet setWithCapacity:[paths count]];
for (NSString *path in paths) {
//获取证书对应的NSData,并且加入集合中
NSData *certificateData = [NSData dataWithContentsOfFile:path];
[certificates addObject:certificateData];
}
//返回证书集合
return [NSSet setWithSet:certificates];
}
/**
返回当前类所在bundle所在的证书集合

@return 证书集合
*/
+ (NSSet *)defaultPinnedCertificates {
static NSSet *_defaultPinnedCertificates = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//获取当前类所在bundle
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
_defaultPinnedCertificates = [self certificatesInBundle:bundle];
});

return _defaultPinnedCertificates;
}
/**
返回默认的安全认证策略,在这里是验证系统的证书。这个策略不允许非法证书、验证主机名、不验证证书内容和公钥

@return 返回认证策略
*/
+ (instancetype)defaultPolicy {
AFSecurityPolicy *securityPolicy = [[self alloc] init];
securityPolicy.SSLPinningMode = AFSSLPinningModeNone;

return securityPolicy;
}

/**
根据指定的认证策略和默认的证书列表初始化一个`AFSecurityPolicy`对象

@param pinningMode 认证策略
@return `AFSecurityPolicy`对象
*/
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode {
return [self policyWithPinningMode:pinningMode withPinnedCertificates:[self defaultPinnedCertificates]];
}

/**
通过制定的认证策略`pinningMode`和证书集合`pinnedCertificates`来初始化一个`AFSecurityPolicy`对象

@param pinningMode 认证模型
@param pinnedCertificates 证书集合
@return AFSecurityPolicy对象
*/
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet *)pinnedCertificates {
AFSecurityPolicy *securityPolicy = [[self alloc] init];
securityPolicy.SSLPinningMode = pinningMode;
//设置`_pinnedCertificates`和`pinnedPublicKeys`属性,分别对应证书集合和公钥集合
[securityPolicy setPinnedCertificates:pinnedCertificates];
//返回初始化成功的`AFSecurityPolicy`
return securityPolicy;
}

- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
//默认是要认证主机名称
self.validatesDomainName = YES;

return self;
}

/**
通过指定的证书结合获取到对应的公钥集合。然后赋值给`pinnedPublicKeys`属性
@param pinnedCertificates 证书集合
*/
- (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;
}
}

#pragma mark -
/**
为serverTrust对象指定认证策略,如果domain不为nil,则包括对主机名的认证。这个方法必须在接受到`authentication challenge`返回的时候调用。
SecTrustRef可以理解为桥接证书与认证策略的对象,他关联指定的证书与认证策略

@param serverTrust 服务器的X.509标准的证书数据
@param domain 认证服务器的主机名。如果是nil,则不会对主机名进行认证。
@return serverTrust是否通过认证
*/
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain
{
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对象指定认证策略
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);

if (self.SSLPinningMode == AFSSLPinningModeNone) {
return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
} else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
return NO;
}
//根据证书验证策略、数字签名认证策略、其他认证策略来处理不同情况
switch (self.SSLPinningMode) {
case AFSSLPinningModeNone://不验证公钥和证书
default:
return NO;
case AFSSLPinningModeCertificate: {//验证整个证书
NSMutableArray *pinnedCertificates = [NSMutableArray array];
//根据指定证书获取,获取对应的证书对象
for (NSData *certificateData in self.pinnedCertificates) {
[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
}
//把证书与serverTrust关联起来
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)
//获取serverTrust证书链。直到根证书。
NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
//如果`pinnedCertificates`包含`serverTrust`对象对应的证书链的根证书。则返回true
for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
return YES;
}
}

return NO;
}
case AFSSLPinningModePublicKey: {//只验证证书里面的数字签名
NSUInteger trustedPublicKeyCount = 0;
//根据serverTrust对象和SecPolicyCreateBasicX509认证策略,获取对应的公钥集合
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

+ (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

最后原文地址,demo地址


...

...

00:00
00:00