AFNetworking 之于 https 认证

写在开头:
  • 本来这篇内容准备写在AFNetworking到底做了什么?(三)中的,但是因为我想在三中完结这个系列,碍于篇幅所限、并且这一块内容独立性比较强,所以单独拎出来,写成一篇。
  • 本文从源码的角度,去分析AFNetworking对https的认证过程。旨在让读者明白我们去做https请求:
    • 如果使用AF,需要做什么。
    • 不使用的话,直接用原生NSUrlSession,又需要做什么。
    • 当我们使用自签证书的https,又需要注意哪些问题。
  • 单独看并不影响阅读。如果有需要了解更多AF相关内容,可以关注楼主的系列文章: AFNetworking到底做了什么? AFNetworking到底做了什么?(二)
那么正文开始了:

简单的理解下https:https在http请求的基础上多加了一个证书认证的流程。认证通过之后,数据传输都是加密进行的。 关于https的更多概念,我就不赘述了,网上有大量的文章,小伙伴们可以自行查阅。在这里大概的讲讲https的认证过程吧,如下图所示:

122702646-c6f158b5d4ad4602

 

https单向认证过程.jpg

1. 客户端发起HTTPS请求 这个没什么好说的,就是用户在浏览器里输入一个https网址,然后连接到server的443端口。 2. 服务端的配置 采用HTTPS协议的服务器必须要有一套数字证书,可以自己制作,也可以向组织申请。区别就是自己颁发的证书需要客户端验证通过,才可以继续访问,而使用受信任的公司申请的证书则不会弹出提示页面。这套证书其实就是一对公钥和私钥。如果对公钥和私钥不太理解,可以想象成一把钥匙和一个锁头,只是全世界只有你一个人有这把钥匙,你可以把锁头给别人,别人可以用这个锁把重要的东西锁起来,然后发给你,因为只有你一个人有这把钥匙,所以只有你才能看到被这把锁锁起来的东西。 3. 传送证书 这个证书其实就是公钥,只是包含了很多信息,如证书的颁发机构,过期时间等等。 4. 客户端解析证书 这部分工作是有客户端的TLS/SSL来完成的,首先会验证公钥是否有效,比如颁发机构,过期时间等等,如果发现异常,则会弹出一个警告框,提示证书存在问题。如果证书没有问题,那么就生成一个随机值。然后用证书对该随机值进行加密。就好像上面说的,把随机值用锁头锁起来,这样除非有钥匙,不然看不到被锁住的内容。 5. 传送加密信息 这部分传送的是用证书加密后的随机值,目的就是让服务端得到这个随机值,以后客户端和服务端的通信就可以通过这个随机值来进行加密解密了。 6. 服务段解密信息 服务端用私钥解密后,得到了客户端传过来的随机值(私钥),然后把内容通过该值进行对称加密。所谓对称加密就是,将信息和私钥通过某种算法混合在一起,这样除非知道私钥,不然无法获取内容,而正好客户端和服务端都知道这个私钥,所以只要加密算法够彪悍,私钥够复杂,数据就够安全。 7. 传输加密后的信息 这部分信息是服务段用私钥加密后的信息,可以在客户端被还原。 8. 客户端解密信息 客户端用之前生成的私钥解密服务段传过来的信息,于是获取了解密后的内容。整个过程第三方即使监听到了数据,也束手无策。

这就是整个https验证的流程了。简单总结一下:

  • 就是用户发起请求,服务器响应后返回一个证书,证书中包含一些基本信息和公钥。
  • 用户拿到证书后,去验证这个证书是否合法,不合法,则请求终止。
  • 合法则生成一个随机数,作为对称加密的密钥,用服务器返回的公钥对这个随机数加密。然后返回给服务器。
  • 服务器拿到加密后的随机数,利用私钥解密,然后再用解密后的随机数(对称密钥),把需要返回的数据加密,加密完成后数据传输给用户。
  • 最后用户拿到加密的数据,用一开始的那个随机数(对称密钥),进行数据解密。整个过程完成。

当然这仅仅是一个单向认证,https还会有双向认证,相对于单向认证也很简单。仅仅多了服务端验证客户端这一步。感兴趣的可以看看这篇:Https单向认证和双向认证。

了解了https认证流程后,接下来我们来讲讲AFSecurityPolicy这个类,AF就是用这个类来满足我们各种https认证需求。

在这之前我们来看看AF用来做https认证的代理:

更多的这个方法的细节问题,可以看注释,或者查阅楼主之前的相关文章,都有去讲到这个代理方法。在这里我们大概的讲讲这个方法做了什么: 1)首先指定了https为默认的认证方式。 2)判断有没有自定义Block:sessionDidReceiveAuthenticationChallenge,有的话,使用我们自定义Block,生成一个认证方式,并且可以给credential赋值,即我们需要接受认证的证书。然后直接调用completionHandler,去根据这两个参数,执行系统的认证。至于这个系统的认证到底做了什么,可以看文章最后,这里暂且略过。 3)如果没有自定义Block,我们判断如果服务端的认证方法要求是NSURLAuthenticationMethodServerTrust,则只需要验证服务端证书是否安全(即https的单向认证,这是AF默认处理的认证方式,其他的认证方式,只能由我们自定义Block的实现) 4)接着我们就执行了AFSecurityPolicy相关的一个方法,做了一个AF内部的一个https认证:

AF默认的处理是,如果这行返回NO、说明AF内部认证失败,则取消https认证,即取消请求。返回YES则进入if块,用服务器返回的一个serverTrust去生成了一个认证证书。(注:这个serverTrust是服务器传过来的,里面包含了服务器的证书信息,是用来我们本地客户端去验证该证书是否合法用的,后面会更详细的去讲这个参数)然后如果有证书,则用证书认证方式,否则还是用默认的验证方式。最后调用completionHandler传递认证方式和要认证的证书,去做系统根证书验证。

  • 总结一下这里securityPolicy存在的作用就是,使得在系统底层自己去验证之前,AF可以先去验证服务端的证书。如果通不过,则直接越过系统的验证,取消https的网络请求。否则,继续去走系统根证书的验证。
接下来我们看看AFSecurityPolicy内部是如果做https认证的:

如下方式,我们可以创建一个securityPolicy

内部创建:

默认指定了一个SSLPinningMode模式为AFSSLPinningModeNone。 对于AFSecurityPolicy,一共有4个重要的属性:

它们的作用我添加在注释里了,第一条就是AFSSLPinningMode, 共提供了3种验证方式:

我们接着回到代理https认证的这行代码上:

 

  • 我们传了两个参数进去,一个是SecTrustRef类型的serverTrust,这是什么呢?我们看到苹果的文档介绍如下:

    CFType used for performing X.509 certificate trust evaluations.

    大概意思是用于执行X。509证书信任评估, 再讲简单点,其实就是一个容器,装了服务器端需要验证的证书的基本信息、公钥等等,不仅如此,它还可以装一些评估策略,还有客户端的锚点证书,这个客户端的证书,可以用来和服务端的证书去匹配验证的。

  • 除此之外还把服务器域名传了过去。

我们来到这个方法,代码如下:

代码的注释很多,这一块确实比枯涩,大家可以参照着源码一起看,加深理解。

  • 这个方法是AFSecurityPolicy最核心的方法,其他的都是为了配合这个方法。这个方法完成了服务端的证书的信任评估。我们总结一下这个方法做了什么(细节可以看注释):
    1. 根据模式,如果是AFSSLPinningModeNone,则肯定是返回YES,不论是自签还是公信机构的证书。
    2. 如果是AFSSLPinningModeCertificate,则从serverTrust中去获取证书链,然后和我们一开始初始化设置的证书集合self.pinnedCertificates去匹配,如果有一对能匹配成功的,就返回YES,否则NO。 看到这可能有小伙伴要问了,什么是证书链?下面这段是我从百科上摘来的:

       

      证书链由两个环节组成—信任锚(CA 证书)环节和已签名证书环节。自我签名的证书仅有一个环节的长度—信任锚环节就是已签名证书本身。

      简单来说,证书链就是就是根证书,和根据根证书签名派发得到的证书。

    3. 如果是AFSSLPinningModePublicKey公钥验证,则和第二步一样还是从serverTrust,获取证书链每一个证书的公钥,放到数组中。和我们的self.pinnedPublicKeys,去配对,如果有一个相同的,就返回YES,否则NO。至于这个self.pinnedPublicKeys,初始化的地方如下:

       

      AF复写了设置证书的set方法,并同时把证书中每个公钥放在了self.pinnedPublicKeys中。

这个方法中关联了一系列的函数,我在这边按照调用顺序一一列出来(有些是系统函数,不在这里列出,会在下文集体描述作用):

函数一:AFServerTrustIsValid

 

 

  • 这个方法用来验证serverTrust是否有效,其中主要是交由系统APISecTrustEvaluate来验证的,它验证完之后会返回一个SecTrustResultType枚举类型的result,然后我们根据这个result去判断是否证书是否有效。
  • 其中比较有意思的是,它调用了一个系统定义的宏函数__Require_noErr_Quiet,函数定义如下: 这个函数主要作用就是,判断errorCode是否为0,不为0则,程序用goto跳到exceptionLabel位置去执行。这个exceptionLabel就是一个代码位置标识,类似上面的_out。 说它有意思的地方是在于,它用了一个do…while(0)循环,循环条件为0,也就是只执行一次循环就结束。对这么做的原因,楼主百思不得其解…看来系统原生API更是高深莫测…经冰霜大神的提醒,这么做是为了适配早期的API??!
函数二、三(两个函数类似,所以放在一起):获取serverTrust证书链证书,获取serverTrust证书链公钥

 

 

两个方法功能类似,都是调用了一些系统的API,利用For循环,获取证书链上每一个证书或者公钥。具体内容看源码很好理解。唯一需要注意的是,这个获取的证书排序,是从证书链的叶节点,到根节点的。

函数四:判断公钥是否相同

 

方法适配了各种运行环境,做了匹配的判断。

接下来列出验证过程中调用过得系统原生函数:

 

其功能如注释,大家可以对比着源码,去加以理解~

132702646-98c6dc4cddc9aef6 可能看到这,又有些小伙伴迷糊了,讲了这么多,那如果做https请求,真正需要我们自己做的到底是什么呢?这里来解答一下,分为以下两种情况:
  1. 如果你用的是付费的公信机构颁发的证书,标准的https,那么无论你用的是AF还是NSUrlSession,什么都不用做,代理方法也不用实现。你的网络请求就能正常完成。
  2. 如果你用的是自签名的证书:
    • 首先你需要在plist文件中,设置可以返回不安全的请求(关闭该域名的ATS)。
    • 其次,如果是NSUrlSesion,那么需要在代理方法实现如下: 其实上述就是AF的相对于自签证书的实现的简化版。 如果是AF,你则需要设置policy: 当然还可以根据需求,你可以去验证证书或者公钥,前提是,你把自签的服务端证书,或者自签的CA根证书导入到项目中:

       

      142702646-4a0eb2f182322d25

       

      证书.png

      并且如下设置证书:

      这样你就可以使用AF的不同AFSSLPinningMode去验证了。

最后总结一下,AF之于https到底做了什么:
  • AF可以让你在系统验证证书之前,就去自主验证。然后如果自己验证不正确,直接取消网络请求。否则验证通过则继续进行系统验证。
  • 讲到这,顺便提一下,系统验证的流程:
    • 系统的验证,首先是去系统的根证书找,看是否有能匹配服务端的证书,如果匹配,则验证成功,返回https的安全数据。
    • 如果不匹配则去判断ATS是否关闭,如果关闭,则返回http不安全连接的数据。如果开启ATS,则拒绝这个请求,请求失败。

总之一句话:AF的验证方式不是必须的,但是对有特殊验证需求的用户确是必要的

posted @ 2016-12-11 15:08  天师符  阅读(596)  评论(0编辑  收藏  举报