AFNetworking 3.0 源码解读 之 AFSecurityPolicy

原文链接:http://www.tuicool.com/articles/eaiEba3

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

在解释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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容

  • 本想在这篇文章中单独写AFNetworking 3.0中AFSecurityPolicy的源码阅读笔记的。但随着源...
    WeiHing阅读 2,559评论 1 13
  • 一、作用 不使用SSL/TLS的HTTP通信,就是不加密的通信。所有信息明文传播,带来了三大风险。 (1)窃听风险...
    XLsn0w阅读 10,500评论 2 44
  • 原文地址 http://blog.csdn.net/u012409247/article/details/4985...
    0fbf551ff6fb阅读 3,516评论 0 13
  • 前面两篇文章中关于 HTTP 相关知识基本上介绍的差不多了,这篇文章是对 HTTP 协议的补充,主要介绍以下三点内...
    lijiankun24阅读 1,305评论 2 3
  • 在车窗的眼睛里 我的脸被撕成完好的黑色纸片 快一点 再快一点抵达吧 我们终坐在一起 我食之物滚烫更令人焦灼 你盛的...
    苏格拉李阅读 295评论 1 6