AFNetworking 3.0 源码解读(二)之 AFSecurityPolicy

在我们平时的开发中,对网络连接安全方面所做的努力,应该占据很重要的位置。

在解释AFSecurityPolicy之前,我们先把基础的http/https 知识简单的普及一下。获取这方面的信息可通过这本书:图解HTTP 

HTTP:

1.HTTP协议用于客户端和服务器端之间的通信

2.通过请求和相应的交换达成通信

客户端请求:

 

服务器端响应:

3.HTTP是不保存状态的协议

HTTP自身不会对请求和相应之间的通信状态进行保存。什么意思呢?就是说,当有新的请求到来的时候,HTTP就会产生新的响应,对之前的请求和响应的保温信息不做任何存储。这也是为了快速的处理事务,保持良好的可伸展性而特意设计成这样的。

4.请求URI定位资源

URI算是一个位置的索引,这样就能很方便的访问到互联网上的各种资源。

5.告知服务器意图的HTTP方法

①GET: 直接访问URI识别的资源,也就是说根据URI来获取资源。

②POST: 用来传输实体的主体。

③PUT: 用来传输文件。

④HEAD: 用来获取报文首部,和GET方法差不多,只是响应部分不会返回主体内容。

⑤DELETE: 删除文件,和PUT恰恰相反。按照请求的URI来删除指定位置的资源。

⑥OPTIONS: 询问支持的方法,用来查询针对请求URI指定的资源支持的方法。

⑦TRACE: 追踪路径,返回服务器端之前的请求通信环信息。

⑧CONNECT: 要求用隧道协议连接代理,要求在与代理服务器通信时简历隧道,实现用隧道协议进行TCP通信。SSL(Secure Sockets Layer)和TLS(Transport Layer Security)就是把通信内容加密后进行隧道传输的。

6.管线化让服务器具备了相应多个请求的能力

7.Cookie让HTTP有迹可循

HTTP是一套很简单通信协议,因此也非常的高效。但是由于通信数据都是明文发送的,很容易被拦截后造成破坏。在互联网越来越发达的时代,对通信数据的安全要求也越来越高。

 

HTTPS

HTTPS是一个通信安全的解决方案,可以说相对已经非常安全。为什么它会是一个很安全的协议呢?下边会做出解释

大家可以看看这篇文章,解释的很有意思 。

 《简单粗暴系列之HTTPS原理》

HTTP + 加密 + 认证 + 完整性保护 = HTTPS

其实HTTPS是身披SSL外壳的HTTP,这句话怎么理解呢?

大家应该都知道HTTP是应用层的协议,但HTTPS并非是应用层的一种新协议,只是HTTP通信接口部分用SSL或TLS协议代替而已。

通常 HTTP 直接和TCP通信,当使用SSL时就不同了。要先和SSL通信,再由SSL和TCP通信。 

这里再说一些关于加密的题外话:

现如今,通常加密和解密的算法都是公开的。举个例子: a * b = 200,加入a是你知道的密码,b是需要被加密的数据,200 是加密后的结果。那么这里这个*号就是一个很简单的加密算法。这个算法是如此简单。但是如果想要在不知道a和b其中一个的情况下进行破解也是很困难的。就算我们知道了200 然后得到a b 这个也很难。假设知道了密码a 那么b就很容易算出b = 200 / a 。 

实际中的加密算法比这个要复杂的多。

介绍两种常用加密方法:

1.共享密钥加密

2.公开密钥加密

共享密钥加密就是加密和解密通用一个密钥,也称为对称加密。优点是加密解密速度快,缺点是一旦密钥泄露,别人也能解密数据。

公开密钥加密恰恰能解决共享密钥加密的困难,过程是这样的:

①发文方使用对方的公开密钥进行加密

②接受方在使用自己的私有密钥进行解密

关于公开密钥,也就是非对称加密 可以看看这篇文章 RSA算法原理

原理都是一样的,这个不同于刚才举得a和b的例子,就算知道了结果和公钥,破解出被机密的数据是非常难的。这里边主要涉及到了复杂的数学理论。

HTTPS采用混合加密机制

HTTPS采用共享密钥加密和公开密钥加密两者并用的混合加密机制。

好了我们大概已经知道了HTTPS是如何加密的了,那么这个相互认证过程是怎么样的呢 ?

在网上看到了这篇博客,http://blog.csdn.net/yuwuchaio/article/details/50469183 把他描述的剪切下来了

==================================================================

==================================================================

注意黄色的部分,这个指明了,我们平时使用的一个场景。这篇文章会很长,不仅仅是为了解释HTTPS,还为了能够增加记忆,当日后想看看的时候,就能通过读这边文章想起大部分的HTTPS的知识。下边解释一些更加详细的HTTPS过程。

好了HTTPS就说到着了,AFSecurityPolicy这个类其实就是为了验证证书是否正确

 

 还是先看头文件里边有什么东西。要实现认证功能需要添加系统的Security,这个是必须的。

下边的这个枚举值的意思的是:

1. AFSSLPinningModeNone    代表无条件信任服务器的证书

2. AFSSLPinningModePublicKey 代表会对服务器返回的证书中的PublicKey进行验证,通过则通过,否则不通过

3. AFSSLPinningModeCertificate 代表会对服务器返回的证书同本地证书全部进行校验,通过则通过,否则不通过

 

 

说的是AFSecurityPolicy 用来评价通过X.509(数字证书的标准)的数字证书和公开密钥进行的安全网络连接是否值得信任。在应用内添加SSL证书能够有效的防止中间人的攻击和安全漏洞。强烈建议涉及用户敏感或隐私数据或金融信息的应用全部网络连接都采用使用SSL的HTTPS连接。

 

 

返回SSL Pinning的类型。默认的是AFSSLPinningModeNone。

 

 

这个属性保存着所有的可用做校验的证书的集合。AFNetworking默认会搜索工程中所有.cer的证书文件。如果想制定某些证书,可使用certificatesInBundle在目标路径下加载证书,然后调用policyWithPinningMode:withPinnedCertificates创建一个本类对象。

注意: 只要在证书集合中任何一个校验通过,evaluateServerTrust:forDomain: 就会返回true,即通过校验。

 

 

 使用允许无效或过期的证书,默认是不允许。

 

 

是否验证证书中的域名domain

 

 

返回指定bundle中的证书。如果使用AFNetworking的证书验证 ,就必须实现此方法,并且使用policyWithPinningMode:withPinnedCertificates 方法来创建实例对象。

 

 

 默认的实例对象,默认的认证设置为:

1. 不允许无效或过期的证书

2. 验证domain名称

3. 不对证书和公钥进行验证

 

 

这两个方法没什么好说的,都是创建security policy 的方法。

 

 

核心方法:使用起来是这样的,这个方法AFNetworking在内部调用了。这个后边会说到

1 AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager]; 
2 
3 AFSecurityPolicy *securityPolicy = [[AFSecurityPolicy alloc] init]; 
4 [securityPolicy setAllowInvalidCertificates:NO]; 
5 [securityPolicy setSSLPinningMode:AFSSLPinningModeCertificate]; 
6 [securityPolicy setValidatesDomainName:YES];
7 [securityPolicy setValidatesCertificateChain:NO]; 
8 
9 manager.securityPolicy = securityPolicy;

 

好了本类的头文件已经看完了,接下来看.m

我们先看这个函数

 1 // 在证书中获取公钥
 2 static id AFPublicKeyForCertificate(NSData *certificate) {
 3     id allowedPublicKey = nil;
 4     SecCertificateRef allowedCertificate;
 5     SecCertificateRef allowedCertificates[1];
 6     CFArrayRef tempCertificates = nil;
 7     SecPolicyRef policy = nil;
 8     SecTrustRef allowedTrust = nil;
 9     SecTrustResultType result;
10 
11     // 1. 根据二进制的certificate生成SecCertificateRef类型的证书
12     // NSData *certificate 通过CoreFoundation (__bridge CFDataRef)转换成 CFDataRef
13     // 看下边的这个方法就可以知道需要传递参数的类型
14     /* 
15      SecCertificateRef SecCertificateCreateWithData(CFAllocatorRef __nullable allocator,
16      CFDataRef data) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_2_0);
17      */
18     allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate);
19     
20     // 2.如果allowedCertificate为空,则执行标记_out后边的代码
21     __Require_Quiet(allowedCertificate != NULL, _out);
22 
23     // 3.给allowedCertificates赋值
24     allowedCertificates[0] = allowedCertificate;
25     
26     // 4.新建CFArra: tempCertificates
27     tempCertificates = CFArrayCreate(NULL, (const void **)allowedCertificates, 1, NULL);
28 
29     // 5. 新建policy为X.509
30     policy = SecPolicyCreateBasicX509();
31     
32     // 6.创建SecTrustRef对象,如果出错就跳到_out标记处
33     __Require_noErr_Quiet(SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust), _out);
34     // 7.校验证书的过程,这个不是异步的。
35     __Require_noErr_Quiet(SecTrustEvaluate(allowedTrust, &result), _out);
36 
37     // 8.在SecTrustRef对象中取出公钥
38     allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust);
39 
40 _out:
41     if (allowedTrust) {
42         CFRelease(allowedTrust);
43     }
44 
45     if (policy) {
46         CFRelease(policy);
47     }
48 
49     if (tempCertificates) {
50         CFRelease(tempCertificates);
51     }
52 
53     if (allowedCertificate) {
54         CFRelease(allowedCertificate);
55     }
56 
57     return allowedPublicKey;
58 }

在二进制的文件中获取公钥的过程是这样

① NSData *certificate -> CFDataRef -> (SecCertificateCreateWithData) -> SecCertificateRef allowedCertificate

②判断SecCertificateRef allowedCertificate 是不是空,如果为空,直接跳转到后边的代码

allowedCertificate 保存在allowedCertificates数组中

allowedCertificates -> (CFArrayCreate) -> SecCertificateRef allowedCertificates[1]

⑤根据函数SecPolicyCreateBasicX509() -> SecPolicyRef policy

SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust) -> 生成SecTrustRef allowedTrust

SecTrustEvaluate(allowedTrust, &result) 校验证书

(__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust) -> 得到公钥id allowedPublicKey

这个过程我们平时也不怎么用,了解下就行了,真需要的时候知道去哪里找资料就行了。

这里边值得学习的地方是:

__Require_Quiet 和 __Require_noErr_Quiet 这两个宏定义。

我们看看他们内部是怎么定义的

可以看出这个宏的用途是:当条件返回false时,执行标记以后的代码

 

可以看出这个宏的用途是:当条件抛出异常时,执行标记以后的代码

这样就有很多使用场景了。当必须要对条件进行判断的时候,我们有下边几种方案了

1.#ifdef   这个是编译特性

2. if else  代码层次的判断

3 __Require_XXX 宏

_out 就是一个标记,这段代码__Require_Quiet 到_out之间的代码不会执行

 

再来看看下边的方法,主要是把key到出为NSData 

这个方法是比较两个key是否相等,如果是ios/watch/tv直接使用isEqual方法就可进行比较。应为SecKeyRef本质上是一个struct,是不能直接用isEqual比较的,正好使用上边的那个方法把它转为NSData就可以了。

 

 

来看原文中这段解释

大概意思是分两种方式:下边的自定义的意思是,用户是否是自己主动设置信任的,比如有些弹窗,用户点击了信任

1.用户自定义的,成功是 kSecTrustResultProceed 失败是kSecTrustResultDeny

2.非用户定义的, 成功是kSecTrustResultUnspecified 失败是kSecTrustResultRecoverableTrustFailure

这就不难解释上边最后的那个或判断了。

 

 

 

 

 

 

 

 

 

 

 

 

 1 - (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
 2                   forDomain:(NSString *)domain
 3 {
 4     if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
 5         // https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/OverridingSSLChainValidationCorrectly.html
 6         //  According to the docs, you should only trust your provided certs for evaluation.
 7         //  Pinned certificates are added to the trust. Without pinned certificates,
 8         //  there is nothing to evaluate against.
 9         //
10         //  From Apple Docs:
11         //          "Do not implicitly trust self-signed certificates as anchors (kSecTrustOptionImplicitAnchors).
12         //           Instead, add your own (self-signed) CA certificate to the list of trusted anchors."
13         NSLog(@"In order to validate a domain name for self signed certificates, you MUST use pinning.");
14         return NO;
15     }
16 
17     NSMutableArray *policies = [NSMutableArray array];
18     if (self.validatesDomainName) {
19         [policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
20     } else {
21         [policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
22     }
23 
24     SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
25 
26     // AFSSLPinningModeNone 不校验证书,
27     if (self.SSLPinningMode == AFSSLPinningModeNone) {
28         return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
29     } else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
30         return NO;
31     }
32 
33     
34     // 代码能够走到这里说明两点
35     // 1.通过了根证书的验证
36     // 2.allowInvalidCertificates = YES
37     
38     switch (self.SSLPinningMode) {
39         case AFSSLPinningModeNone:
40         default:
41             return NO;
42         case AFSSLPinningModeCertificate: { // 全部校验
43             NSMutableArray *pinnedCertificates = [NSMutableArray array];
44             for (NSData *certificateData in self.pinnedCertificates) {
45                 [pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
46             }
47             
48             // 把本地的证书设为根证书,即服务器应该信任的证书
49             SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);
50 
51             // 校验能够信任
52             if (!AFServerTrustIsValid(serverTrust)) {
53                 return NO;
54             }
55 
56             // obtain the chain after being validated, which *should* contain the pinned certificate in the last position (if it's the Root CA)
57             NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
58             
59             //  判断本地证书和服务器证书是否相同
60             for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
61                 if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
62                     return YES;
63                 }
64             }
65             
66             return NO;
67         }
68         case AFSSLPinningModePublicKey: {
69             NSUInteger trustedPublicKeyCount = 0;
70             NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);
71 
72             // 找到相同的公钥就通过
73             for (id trustChainPublicKey in publicKeys) {
74                 for (id pinnedPublicKey in self.pinnedPublicKeys) {
75                     if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
76                         trustedPublicKeyCount += 1;
77                     }
78                 }
79             }
80             return trustedPublicKeyCount > 0;
81         }
82     }
83     
84     return NO;
85 }
 1 #pragma mark - NSKeyValueObserving
 2 
 3 + (NSSet *)keyPathsForValuesAffectingPinnedPublicKeys {
 4     return [NSSet setWithObject:@"pinnedCertificates"];
 5 }
 6 
 7 #pragma mark - NSSecureCoding
 8 
 9 + (BOOL)supportsSecureCoding {
10     return YES;
11 }
12 
13 - (instancetype)initWithCoder:(NSCoder *)decoder {
14 
15     self = [self init];
16     if (!self) {
17         return nil;
18     }
19 
20     self.SSLPinningMode = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(SSLPinningMode))] unsignedIntegerValue];
21     self.allowInvalidCertificates = [decoder decodeBoolForKey:NSStringFromSelector(@selector(allowInvalidCertificates))];
22     self.validatesDomainName = [decoder decodeBoolForKey:NSStringFromSelector(@selector(validatesDomainName))];
23     self.pinnedCertificates = [decoder decodeObjectOfClass:[NSArray class] forKey:NSStringFromSelector(@selector(pinnedCertificates))];
24 
25     return self;
26 }
27 
28 - (void)encodeWithCoder:(NSCoder *)coder {
29     [coder encodeObject:[NSNumber numberWithUnsignedInteger:self.SSLPinningMode] forKey:NSStringFromSelector(@selector(SSLPinningMode))];
30     [coder encodeBool:self.allowInvalidCertificates forKey:NSStringFromSelector(@selector(allowInvalidCertificates))];
31     [coder encodeBool:self.validatesDomainName forKey:NSStringFromSelector(@selector(validatesDomainName))];
32     [coder encodeObject:self.pinnedCertificates forKey:NSStringFromSelector(@selector(pinnedCertificates))];
33 }
34 
35 #pragma mark - NSCopying
36 
37 - (instancetype)copyWithZone:(NSZone *)zone {
38     AFSecurityPolicy *securityPolicy = [[[self class] allocWithZone:zone] init];
39     securityPolicy.SSLPinningMode = self.SSLPinningMode;
40     securityPolicy.allowInvalidCertificates = self.allowInvalidCertificates;
41     securityPolicy.validatesDomainName = self.validatesDomainName;
42     securityPolicy.pinnedCertificates = [self.pinnedCertificates copyWithZone:zone];
43 
44     return securityPolicy;
45 }

 

好了,这篇就到这了,大体了解了SSL校验是怎么一回事了,而且知道了该如何操作。

在这里推荐一篇不错的文章    iOS 9之适配ATS

posted @ 2016-07-29 09:05  马在路上  阅读(6899)  评论(3编辑  收藏  举报