在 WWDC 16 中,Apple 表示, 从 2017年1月1日起( 最新消息, 实施时间已延期 ),所有新提交的 App 使用系统组件进行的 HTTP 网络请求都需要是 HTTPS 加密的,否则会导致请求失败而无法通过审核。 精神哥对 HTTPS 的验证过程有一些了解,但对于在iOS中如何实现 HTTPS 验证却不是很清楚,在内网搜索到李晴同学写的这篇文章,阅读后收获不小,分享给大家。 正文本文的目的: 一是简要分析下对服务器身份验证的完整握手过程,二是证书链的验证,三是探索下iOS中原生库NSURLConnection或NSURLSession如何支持实现https。 一、HTTPSHTTPS是承载在TLS/SSL之上的HTTP,相较于HTTP明文数据传输方面所暴露出的缺点,HTTPS具有防止信息被窃听、篡改、劫持,提供信息加密,完整性校验及身份验证等优势。TLS/SSL是安全传输层协议,介于TCP和HTTP之间。TLS1.0是建立在SSL3.0规范之上的,可以理解为SSL3.0的升级版本。目前推荐使用的版本是TLS1.2。 TLS/SSL协议通常分为两层:TLS记录协议(TLS Record Protocol)和TLS握手协议(TLS Handshake Protocol)。 TLS记录协议建立在可靠的传输协议(如TCP)之上,为高层协议提供数据封装、压缩、加密等基本功能的支持。TLS握手协议建立在记录协议之上,用于在实际的数据传输开始前,通讯双方进行身份认证、协商加密算法、交换加密密钥等。除了这俩协议以外,还存在其它三种辅助协议: Changecipher spec 协议用来通知对端从handshake切换到record协议(有点冗余,在TLS1.3里面已经被删掉了)。alert协议,用来通知各种返回码。application data协议,就是把http,smtp等的数据流传入record层做处理并传输。 想象一种场景:通常我们会访问HTTPS://xxx的网站,当你在浏览器地址栏输入支持HTTPS协议的URL地址后,服务器返回的数据会显示在页面上。对于不了解HTTPS协议工作原理的小伙伴可能觉得这个过程很简单:发送请求-服务器响应请求-结果返回并显示。但对于HTTPS而言,在整个发送请求返回数据过程中还涉及到通讯双方证书验证、数据加密、数据完整性校验等。 下面以登录qq邮箱为例,通过Wireshark抓包可以看到如下图:
在浏览器与服务器进行Application Data传输之前,还经历了Client Hello-Server Hello-Client Key Exchange-Change Cipher Spec等过程。而这些过程正是TLS/SSL提供的服务所决定的:
上述单向验证的完整握手过程,总结如下:
第一阶段:ClientHello客户端发起请求,以明文传输请求信息,包含版本信息,加密套件候选列表,压缩算法候选列表,随机数random_C,扩展字段等信息。 第二阶段:ServerHello-ServerHelloDone如上图可以看出这个阶段包含4个过程( 有的服务器是单条发送,有的是合并一起发送)。服务端返回协商的信息结果,包括选择使用的协议版本,选择的加密套件,选择的压缩算法、随机数random_S等,其中随机数用于后续的密钥协商。服务器也会配置并返回对应的证书链Certificate,用于身份验证与密钥交换。然后会发送ServerHelloDone信息用于通知服务器信息发送结束。 第三阶段:证书校验在上图中的5-6之间,客户端这边还需要对服务器返回的证书进行校验。只有证书验证通过后,才能进行后续的通信。(具体分析可参看后续的证书验证过程) 第四阶段:ClientKeyExchange-Finished服务器返回的证书验证合法后, 客户端计算产生随机数字Pre-master,并用server证书中公钥加密,发送给服务器。同时客户端会根据已有的三个随机数根据相应的生成协商密钥。客户端会通知服务器后续的通信都采用协商的通信密钥和加密算法进行加密通信。然后客户端发送Finished消息用于通知客户端信息发送结束。 第五阶段:服务器端生成协商密钥服务器也会根据已有的三个随机数使用相应的算法生成协商密钥,会通知客户端后续的通信都采用协商的通信密钥和加密算法进行加密通信。然后发送Finished消息用于通知服务器信息发送结束。 第六阶段:握手结束在握手阶段结束后,客户端和服务器数据传输开始使用协商密钥进行加密通信。 总结简单来说,HTTPS请求整个过程主要分为两部分。一是握手过程:用于客户端和服务器验证双方身份,协商后续数据传输时使用到的密钥等。二是数据传输过程:身份验证通过并协商好密钥后,通信双方使用协商好的密钥加密数据并进行通信。在握手过程协商密钥时,使用的是非对称密钥交换算法, 密钥交换算法本身非常复杂,密钥交换过程涉及到随机数生成,模指数运算,空白补齐,加密,签名等操作。在数据传输过程中,客户端和服务器端使用协商好的密钥进行对称加密解密。 二、证书PKI (Public Key Infrastructure),公开密钥基础设施。它是一个标准,在这个标准之下发展出的为了实现安全基础服务目的的技术统称为PKI。 权威的第三方机构CA(认证中心)是PKI的核心, CA负责核实公钥的拥有者的信息,并颁发认证”证书”,同时能够为使用者提供证书验证服务。 x.509是PKI中最重要的标准,它定义了公钥证书的基本结构。 证书申请过程
证书验证过程
证书验证的递归过程最终会成功终止,而成功终止的条件是:证书验证过程中遇到了锚点证书,锚点证书通常指:嵌入到操作系统中的根证书(权威证书颁发机构颁发的自签名证书)。 证书验证失败的原因
三、iOS实现支持HTTPS在OC中当使用NSURLConnection或NSURLSession建立URL并向服务器发送https请求获取资源时,服务器会使用HTTP状态码401进行响应(即访问拒绝)。此时NSURLConnection或NSURLSession会接收到服务器需要授权的响应,当客户端授权通过后,才能继续从服务器获取数据。如下图所示:
非自签名证书验证实现在接收到服务器返回的状态码为401的响应后,对于NSURLSession而言,需要代理对象实现URLSession:task:didReceiveChallenge:completionHandler:方法。对于NSURLConnection而言,需要代理对象实现connection:willSendRequestForAuthenticationChallenge: 方法(OS X v10.7和iOS5及以上),对于早期的版本代理对象需要实现代理对象要实现connection:canAuthenticateAgainstProtectionSpace:和connection:didReceiveAuthenticationChallenge:方法。代码如下(参考文档):
当客户端发送https请求后,服务器会返回需要授权的相关信息,然后connection:willSendRequestForAuthenticationChallenge:方法被调用。客户端根据返回的challenge信息,首先获取需要验证的信任对象trust,然后调用SecTrustEvaluate方法是用系统默认的验证方式对信任对象进行验证,当验证通过时,使用该信任对象trust生成证书凭证,然后self.connection使用该凭证继续连接。如下详解: NSURLAuthenticationChallenge包含如下信息:
SecTrustRef表示需要验证的信任对象(Trust Object),在此指的是challenge.protectionSpace.serverTrust。包含待验证的证书和支持的验证方法等。 SecTrustResultType表示验证结果。其中 kSecTrustResultProceed表示serverTrust验证成功,且该验证得到了用户认可(例如在弹出的是否信任的alert框中选择always trust)。 kSecTrustResultUnspecified表示 serverTrust验证成功,此证书也被暗中信任了,但是用户并没有显示地决定信任该证书。 两者取其一就可以认为对serverTrust验证成功。 SecTrustEvaluate函数内部递归地从叶节点证书到根证书验证。使用系统默认的验证方式验证Trust Object,根据上述证书链的验证可知,系统会根据Trust Object的验证策略,一级一级往上,验证证书链上每一级证书有效性。 NSURLCredential表示身份验证证书。URL Lodaing支持3种类型证书:password-based user credentials, certificate-based user credentials, 和certificate-based server credentials(需要验证服务器身份时使用)。因此NSURLCredential可以表示由用户名/密码组合、客户端证书及服务器信任创建的认证信息,适合大部分的认证请求。对于NSURLCredential也存在三种持久化机制:
对于已经验证通过的信任对象,客户端也可以不提供证书凭证。
对于非自签名的证书,即使服务器返回的证书是信任的CA颁发的,而为了确定返回的证书正是客户端需要的证书,这需要本地导入证书,并将证书设置成需要参与验证的锚点证书,再调用SecTrustEvaluate通过本地导入的证书来验证服务器证书是否是可信的。如果服务器证书是这个锚点证书对应CA或者子CA颁发的,或服务器证书本身就是这个锚点证书,则证书信任通过。如下代码(参考文档):
自签名证书验证实现对于自签名证书,这样Trust Object中的服务器证书是不可信任的CA颁发的,直接使用SecTrustEvaluate验证是不会成功的。可以采取下述简单代码绕过HTTPS的验证:
上述代码一般用于当服务器使用自签名证书时,为了方便测试,客户端可以通过该方法信任所有自签名证书。 综上对非自建和自建证书验证过程的分析,可以总结如下:
|