AFNetworking源码解析-https证书相关
本篇说说安全相关的AFSecurityPolicy模块,AFSecurityPolicy用于验证HTTPS请求的证书,先来看看HTTPS的原理和证书相关的几个问题。
HTTPS
HTTPS连接建立过程大致是,客户端和服务端建立一个连接,服务端返回一个证书,客户端里存有各个受信任的证书机构根证书,用这些根证书对服务端 返回的证书进行验证,经验证如果证书是可信任的,就生成一个pre-master secret,用这个证书的公钥加密后发送给服务端,服务端用私钥解密后得到pre-master secret,再根据某种算法生成master secret,客户端也同样根据这种算法从pre-master secret生成master secret,随后双方的通信都用这个master secret对传输数据进行加密解密。
以上是简单过程,中间还有很多细节,详细过程和原理已经有很多文章阐述得很好,就不再复述,推荐一些相关文章:
关于非对称加密算法的原理:RSA算法原理<一>、<二>
关于整个流程:HTTPS那些事<一>、<二>、<三>
关于数字证书:浅析数字证书
这里说下一开始我比较费解的两个问题:
1.证书是怎样验证的?怎样保证中间人不能伪造证书?
首先要知道非对称加密算法的特点,非对称加密有一对公钥私钥,用公钥加密的数据只能通过对应的私钥解密,用私钥加密的数据只能通过对应的公钥解密。
我们来看最简单的情况:一个证书颁发机构(CA),颁发了一个证书A,服务器用这个证书建立https连接。客户端在信任列表里有这个CA机构的根证书。
首先CA机构颁发的证书A里包含有证书内容F,以及证书加密内容F1,加密内容F1就是用这个证书机构的私钥对内容F加密的结果。(这中间还有一次hash算法,略过。)
建立https连接时,服务端返回证书A给客户端,客户端的系统里的CA机构根证书有这个CA机构的公钥,用这个公钥对证书A的加密内容F1解密得 到F2,跟证书A里内容F对比,若相等就通过验证。整个流程大致是:F->CA私钥加密->F1->客户端CA公钥解密->F。 因为中间人不会有CA机构的私钥,客户端无法通过CA公钥解密,所以伪造的证书肯定无法通过验证。
2.什么是SSL Pinning?
可以理解为证书绑定,是指客户端直接保存服务端的证书,建立https连接时直接对比服务端返回的和客户端保存的两个证书是否一样,一样就表明证书 是真的,不再去系统的信任证书机构里寻找验证。这适用于非浏览器应用,因为浏览器跟很多未知服务端打交道,无法把每个服务端的证书都保存到本地,但CS架 构的像手机APP事先已经知道要进行通信的服务端,可以直接在客户端保存这个服务端的证书用于校验。
为什么直接对比就能保证证书没问题?如果中间人从客户端取出证书,再伪装成服务端跟其他客户端通信,它发送给客户端的这个证书不就能通过验证吗?确 实可以通过验证,但后续的流程走不下去,因为下一步客户端会用证书里的公钥加密,中间人没有这个证书的私钥就解不出内容,也就截获不到数据,这个证书的私 钥只有真正的服务端有,中间人伪造证书主要伪造的是公钥。
为什么要用SSL Pinning?正常的验证方式不够吗?如果服务端的证书是从受信任的的CA机构颁发的,验证是没问题的,但CA机构颁发证书比较昂贵,小企业或个人用户 可能会选择自己颁发证书,这样就无法通过系统受信任的CA机构列表验证这个证书的真伪了,所以需要SSL Pinning这样的方式去验证。
AFSecurityPolicy
NSURLConnection已经封装了https连接的建立、数据的加密解密功能,我们直接使用NSURLConnection是可以访问 https网站的,但NSURLConnection并没有验证证书是否合法,无法避免中间人攻击。要做到真正安全通讯,需要我们手动去验证服务端返回的 证书,AFSecurityPolicy封装了证书验证的过程,让用户可以轻易使用,除了去系统信任CA机构列表验证,还支持SSL Pinning方式的验证。使用方法:
1
2
3
4
5
6
7
|
//把服务端证书(需要转换成cer格式)放到APP项目资源里,AFSecurityPolicy会自动寻找根目录下所有cer文件 AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey]; securityPolicy.allowInvalidCertificates = YES; [AFHTTPRequestOperationManager manager].securityPolicy = securityPolicy; [manager GET:@ "https://example.com/" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) { } failure:^(AFHTTPRequestOperation *operation, NSError *error) { }]; |
AFSecurityPolicy分三种验证模式:
AFSSLPinningModeNone
这个模式表示不做SSL pinning,只跟浏览器一样在系统的信任机构列表里验证服务端返回的证书。若证书是信任机构签发的就会通过,若是自己服务器生成的证书,这里是不会通过的。
AFSSLPinningModeCertificate
这个模式表示用证书绑定方式验证证书,需要客户端保存有服务端的证书拷贝,这里验证分两步,第一步验证证书的域名/有效期等信息,第二步是对比服务端返回的证书跟客户端返回的是否一致。
这里还没弄明白第一步的验证是怎么进行的,代码上跟去系统信任机构列表里验证一样调用了SecTrustEvaluate,只是这里的列表换成了客户端保存的那些证书列表。若要验证这个,是否应该把服务端证书的颁发机构根证书也放到客户端里?
AFSSLPinningModePublicKey
这个模式同样是用证书绑定方式验证,客户端要有服务端的证书拷贝,只是验证时只验证证书里的公钥,不验证证书的有效期等信息。只要公钥是正确的,就能保证通信不会被窃听,因为中间人没有私钥,无法解开通过公钥加密的数据。
整个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
|
// AFSecurity.m // // Copyright (c) 2013-2014 AFNetworking (http://afnetworking.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import "AFSecurityPolicy.h" // Equivalent of macro in , without causing compiler warning: // "'DebugAssert' is deprecated: first deprecated in OS X 10.8" //这两个宏方法用于方便地处理调用各种证书方法过程中出现的错误,出现错误后用goto语句直接跳转到结束语 //关于为什么要用 __builtin_expect (x, 0) 而不直接用 x != 0,是为了CPU执行时的性能优化,见这里: #ifndef AF_Require #define AF_Require(assertion, exceptionLabel) \ do { \ if (__builtin_expect(!(assertion), 0)) { \ goto exceptionLabel; \ } \ } while (0) #endif #ifndef AF_Require_noErr #define AF_Require_noErr(errorCode, exceptionLabel) \ do { \ if (__builtin_expect(0 != (errorCode), 0)) { \ goto exceptionLabel; \ } \ } while (0) #endif #if !defined(__IPHONE_OS_VERSION_MIN_REQUIRED) static NSData * AFSecKeyGetData(SecKeyRef key) { CFDataRef data = NULL; AF_Require_noErr(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 defined(__IPHONE_OS_VERSION_MIN_REQUIRED) return [(__bridge id)key1 isEqual:(__bridge id)key2]; #else return [AFSecKeyGetData(key1) isEqual:AFSecKeyGetData(key2)]; #endif } //从证书里取public key static id AFPublicKeyForCertificate(NSData *certificate) { id allowedPublicKey = nil; SecCertificateRef allowedCertificate; SecCertificateRef allowedCertificates[1]; CFArrayRef tempCertificates = nil; SecPolicyRef policy = nil; SecTrustRef allowedTrust = nil; SecTrustResultType result; //取证书SecCertificateRef -> 生成证书数组 -> 生成SecTrustRef -> 从SecTrustRef取PublicKey allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate); AF_Require(allowedCertificate != NULL, _out); allowedCertificates[0] = allowedCertificate; tempCertificates = CFArrayCreate(NULL, (const void **)allowedCertificates, 1, NULL); policy = SecPolicyCreateBasicX509(); AF_Require_noErr(SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust), _out); AF_Require_noErr(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; } static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) { BOOL isValid = NO; SecTrustResultType result; AF_Require_noErr(SecTrustEvaluate(serverTrust, &result), _out); //kSecTrustResultUnspecified:证书通过验证,但用户没有设置这些证书是否被信任 //kSecTrustResultProceed:证书通过验证,用户有操作设置了证书被信任,例如在弹出的是否信任的alert框中选择always trust 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]; } //取出服务端返回证书里所有的public key static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) { SecPolicyRef policy = SecPolicyCreateBasicX509(); CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust); NSMutableArray *trustChain = [NSMutableArray arrayWithCapacity:(NSUInteger)certificateCount]; for (CFIndex i = 0; i 生成证书数组 -> 生成SecTrustRef -> 从SecTrustRef取PublicKey SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i); SecCertificateRef someCertificates[] = {certificate}; CFArrayRef certificates = CFArrayCreate(NULL, (const void **)someCertificates, 1, NULL); SecTrustRef trust; AF_Require_noErr(SecTrustCreateWithCertificates(certificates, policy, &trust), _out); SecTrustResultType result; AF_Require_noErr(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 (readwrite, nonatomic, strong) NSArray *pinnedPublicKeys; @end @implementation AFSecurityPolicy + (NSArray *)defaultPinnedCertificates { //默认证书列表,遍历根目录下所有.cer文件 static NSArray *_defaultPinnedCertificates = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSBundle *bundle = [NSBundle bundleForClass:[self class]]; NSArray *paths = [bundle pathsForResourcesOfType:@ "cer" inDirectory:@ "." ]; NSMutableArray *certificates = [NSMutableArray arrayWithCapacity:[paths count]]; for (NSString *path in paths) { NSData *certificateData = [NSData dataWithContentsOfFile:path]; [certificates addObject:certificateData]; } _defaultPinnedCertificates = [[NSArray alloc] initWithArray:certificates]; }); return _defaultPinnedCertificates; } + (instancetype)defaultPolicy { AFSecurityPolicy *securityPolicy = [[self alloc] init]; securityPolicy.SSLPinningMode = AFSSLPinningModeNone; return securityPolicy; } + (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode { AFSecurityPolicy *securityPolicy = [[self alloc] init]; securityPolicy.SSLPinningMode = pinningMode; securityPolicy.validatesDomainName = YES; [securityPolicy setPinnedCertificates:[self defaultPinnedCertificates]]; return securityPolicy; } - (id)init { self = [ super init]; if (!self) { return nil; } self.validatesCertificateChain = YES; return self; } #pragma mark - - (void)setPinnedCertificates:(NSArray *)pinnedCertificates { _pinnedCertificates = pinnedCertificates; if (self.pinnedCertificates) { //预先取出public key,用于AFSSLPinningModePublicKey方式的验证 NSMutableArray *mutablePinnedPublicKeys = [NSMutableArray arrayWithCapacity:[self.pinnedCertificates count]]; for (NSData *certificate in self.pinnedCertificates) { id publicKey = AFPublicKeyForCertificate(certificate); if (!publicKey) { continue ; } [mutablePinnedPublicKeys addObject:publicKey]; } self.pinnedPublicKeys = [NSArray arrayWithArray:mutablePinnedPublicKeys]; } else { self.pinnedPublicKeys = nil; } } #pragma mark - - (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust { return [self evaluateServerTrust:serverTrust forDomain:nil]; } - (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain { 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); //向系统内置的根证书验证服务端返回的证书是否合法 //若使用自签名证书,这里的验证是会不合法的,需要设allowInvalidCertificates = YES if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) { return NO; } //取出服务端返回的证书 NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust); switch (self.SSLPinningMode) { case AFSSLPinningModeNone: //两种情况走到这里, //一是通过系统证书验证,返回认证成功 //二是没通过验证,但allowInvalidCertificates = YES,也就是说完全不认证直接返回认证成功 return YES; //验证整个证书 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; } if (!self.validatesCertificateChain) { return YES; } //整个证书链都跟本地的证书匹配才给过 NSUInteger trustedCertificateCount = 0; for (NSData *trustChainCertificate in serverCertificates) { if ([self.pinnedCertificates containsObject:trustChainCertificate]) { trustedCertificateCount++; } } return trustedCertificateCount == [serverCertificates count]; } //只验证证书的public key case AFSSLPinningModePublicKey: { NSUInteger trustedPublicKeyCount = 0; NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust); //如果不用验证整个证书链,取第一个也就是真正会使用的那个证书验证就行 if (!self.validatesCertificateChain && [publicKeys count] > 0) { publicKeys = @[[publicKeys firstObject]]; } //在本地证书里搜索相等的public key,记录找到个数 for (id trustChainPublicKey in publicKeys) { for (id pinnedPublicKey in self.pinnedPublicKeys) { if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) { trustedPublicKeyCount += 1; } } } //验证整个证书链的情况:每个public key都在本地找到算验证通过 //验证单个证书的情况:找到一个算验证通过 return trustedPublicKeyCount > 0 && ((self.validatesCertificateChain && trustedPublicKeyCount == [serverCertificates count]) || (!self.validatesCertificateChain && trustedPublicKeyCount >= 1)); } } return NO; } #pragma mark - NSKeyValueObserving + (NSSet *)keyPathsForValuesAffectingPinnedPublicKeys { return [NSSet setWithObject:@ "pinnedCertificates" ]; } @end |
AFNetworking源码解析-https证书相关的更多相关文章
- iOS网络请求-AFNetworking源码解析
趁着端午节日,自己没有什么过多的安排,准备花4-5天左右,针对网络请求源码AFNetworking和YTKNetwork进行解析以及这两年多iOS实际开发经验(其实YTKNetwork也是对AFNet ...
- AFNetworking 源码解析
3.0 之后,就取消了NSOperation的控制. 因为根据Apple Developer Document的文档 https://developer.apple.com/documentation ...
- Retrofit源码解析(上)
简介Retrofit是Square公司开发的一款针对Android网络请求的框架,官网地址http://square.github.io/retrofit/ ,在官网上有这样的一句话介绍retrofi ...
- [源码解析] PyTorch 分布式 Autograd (3) ---- 上下文相关
[源码解析] PyTorch 分布式 Autograd (3) ---- 上下文相关 0x00 摘要 我们已经知道 dist.autograd 如何发送和接受消息,本文再来看看如何其他支撑部分,就是如 ...
- Spring中AOP相关的API及源码解析
Spring中AOP相关的API及源码解析 本系列文章: 读源码,我们可以从第一行读起 你知道Spring是怎么解析配置类的吗? 配置类为什么要添加@Configuration注解? 谈谈Spring ...
- 从源码解析Nginx对 Native aio支持_运维_youbingchen的博客-CSDN博客 https://blog.csdn.net/youbingchen/article/details/51767587
从源码解析Nginx对 Native aio支持_运维_youbingchen的博客-CSDN博客 https://blog.csdn.net/youbingchen/article/details/ ...
- fork()相关的源码解析
fork()的真正执行采用的是do_fork()函数,所以下文将从do_fork()函数对fork()进行源码解析.下图是do_fork()的源码函数设计: 从上图我们可以看到do_fork()涉及到 ...
- 【原】AFNetworking源码阅读(三)
[原]AFNetworking源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇的话,主要是讲了如何通过构建一个request来生成一个data tas ...
- iOS开发之Alamofire源码解析
今天博客中的Alamofire源码的版本是以3.4版本为例.上篇博客系统的对NSURLSession相关的东西进行了详细的解析,详情请看<详解NSURLSession>,为了就是给本篇博客 ...
随机推荐
- SSM框架下实现导入功能
何叫导入?简单说,导入就是把excel表格里的数据插入到数据库里.我这里做的是支持.xls和.xlsx格式. 因为是自己做案例记录,那些jar包什么的就不细说了,主要讲实现和步骤,看代码: 先在你的项 ...
- UVa - 12664 - Interesting Calculator
先上题目: 12664 Interesting CalculatorThere is an interesting calculator. It has 3 rows of button.• Row ...
- static_cast、dynamic_cast reinterpret_cast
关于强制类型转换的问题,很多书都讨论过,写的最详细的是C++ 之父的<C++ 的设计和演化>.最好的解决方法就是不要使用C风格的强制类型转换,而是使用标准C++的类型转换符:static_ ...
- 洛谷—— P2733 家的范围 Home on the Range
https://www.luogu.org/problem/show?pid=2733 题目背景 农民约翰在一片边长是N (2 <= N <= 250)英里的正方形牧场上放牧他的奶牛.(因 ...
- Linux统计行数命令wc(转)
Linux wc命令用于计算字数. 利用wc指令我们可以计算文件的Byte数.字数.或是列数,若不指定文件名称.或是所给予的文件名为"-",则wc指令会从标准输入设备读取数据. 语 ...
- Android面试准备 第一天 第2-4例
參考:http://blog.csdn.net/lmj623565791/article/details/24015867. .假设有个100M大的文件.须要上传至server中.而serverfor ...
- android获取当前软件版本号号和版本号名称
<span style="font-size:18px;">/** * 获取本地软件版本号 */ public static int getLocalVersion(C ...
- 解析HTTP协议六种请求方法
标准Http协议支持六种请求方法,即: 1,GET 2,HEAD 3,PUT 4,DELETE 5,POST 6,OPTIONS 但其实我们大部分情况下只用到了GET和POST.如果想设计一个符合RE ...
- elasticsearch _field_stats 源码分析
_field_stats 实现的功能:https://www.elastic.co/guide/en/elasticsearch/reference/5.6/search-field-stats.ht ...
- pandas把多个sheet读进一个DataFrame
#!/usr/bin/python import pandas as pd import collections df_dict = pd.read_excel('c:\data\machines.x ...