AFNetworking2.0源码解析

时间:2022-10-26 11:00:59

本篇说说安全相关的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