HTTP的基本概念和基础
1.1 TCP/IP 协议族-(HTTP属于TCP/IP协议族的一员)
image.png图:TCP/IP 是互联网相关的各类协议族的总称
TCP/IP 协议族里重要的一点就是分层。TCP/IP 协议族按层次分别分
为以下 4 层:应用层、传输层、网络层和数据链路层。
应用层
TCP/IP 协议族内预存了各类通用的应用服务。比如,FTP(File
Transfer Protocol,文件传输协议)和 DNS(Domain Name System,域
名系统)服务就是其中两类。
HTTP 协议也处于该层。
传输层
传输层对上层应用层,提供处于网络连接中的两台计算机之间的数据
传输。
在传输层有两个性质不同的协议:TCP(Transmission Control
Protocol,传输控制协议)和 UDP(User Data Protocol,用户数据报
协议)。
网络层(又名网络互连层)
网络层用来处理在网络上流动的数据包。数据包是网络传输的最小数
据单位。该层规定了通过怎样的路径(所谓的传输路线)到达对方计
算机,并把数据包传送给对方。
与对方计算机之间通过多台计算机或网络设备进行传输时,网络层所
起的作用就是在众多的选项内选择一条传输路线。
链路层(又名数据链路层,网络接口层)
用来处理连接网络的硬件部分。包括控制操作系统、硬件的设备驱
动、NIC(Network Interface Card,网络适配器,即网卡),及光纤等
物理可见部分(还包括连接器等一切传输媒介)。硬件上的范畴均在
链路层的作用范围之内
1.2 HTTP数据的发送图解
image.png image.png1.3 HTTP和各个协议的关系
图解HTTP 彩色版.jpg二、HTTP通信的基本概念和组成
image.png人们最初设想的基本理念是:借助多文档之间相互关联形成的超文本
(HyperText),连成可相互参阅的 WWW(World Wide Web,万维
网)。
URI和URL
URI 是 Uniform Resource Identifier,用字符串标识某一互联网资源,而 URL 表示资源的地点(互联网上所处的位置)。可见 URL 是 URI 的子集。
绝对URI的格式
- 协议方案名,不区分大小写,最后附上:结尾。
- 登录信息认证(可选)
- 服务器地址,可以采用域名 (比如: hackr.jp)这种DNS可以解析的名称,或是192.168.1.1以及[0:0:0:0:0:0:0:1]这一类IPV4,IPV6(用方括号括起来,128bit,每一16位为一段)这样的IP地址名。
- 服务器网络的端口号(可选),如果省略则自动使用默认的端口号。
- 带层级的文件路径,这个和UNIX谢勇的文件目录结构类似。
- 查询字符串(可选),可以使用查询字符串传入任意参数。
- 片段标识符(可选),使用片段标识符可以标记处以获取资源中的子资源。
基本概念
客户端(Client):移动应用(iOS、android等应用)
服务器(Server):为客户端提供服务、提供数据、提供资源的机器
请求(Request):客户端向服务器索取数据的一种行为
响应(Response):服务器对客户端的请求做出的反应,一般指返回数据给客户端
URL
HTTP协议的作用
HTTP的全称是Hypertext Transfer Protocol,超文本传输协议
规定客户端和服务器之间的数据传输格式
让客户端和服务器能有效地进行数据沟通
HTTP/1.1协议中,定义了8种发送http请求的方法
GET、POST、OPTIONS、HEAD、PUT、DELETE、TRACE、CONNECT、PATCH
根据HTTP协议的设计初衷,不同的方法对资源有不同的操作方式
PUT :增
DELETE :删
POST:改
GET:查
最常用的是GET和POST(实际上GET和POST都能办到增删改查)
要想使用GET和POST请求跟服务器进行交互,得先了解一个概念
参数
就是传递给服务器的具体数据,比如登录时的帐号、密码
GET和POST对比
GET和POST的主要区别表现在数据传递上
GET
在请求URL后面以?的形式跟上发给服务器的参数,多个参数之间用&隔开,比如
http://ww.test.com/login?username=123&pwd=234&type=JSON
由于浏览器和服务器对URL长度有限制,因此在URL后面附带的参数是有限制的,通常不能超过1KB
POST
发给服务器的参数全部放在请求体中
理论上,POST传递的数据量没有限制(具体还得看服务器的处理能力)
如何选择Get和Post
如果要传递大量数据,比如文件上传,只能用POST请求
GET的安全性比POST要差些,如果包含机密\敏感信息,建议用POST
如果仅仅是索取数据(数据查询),建议使用GET
如果是增加、修改、删除数据,建议使用POST
HTTP的通信过程 - 请求
请求头: 包含了对客户端的环境描述、客户端请求信息等
GET /minion.png HTTP/1.1 // 包含了请求方法、请求资源路径、HTTP协议版本
Host: 120.25.226.186:32812 // 客户端想访问的服务器主机地址
User-Agent: Mozilla/5.0 // 客户端的类型,客户端的软件环境
Accept: text/html, / // 客户端所能接收的数据类型
Accept-Language: zh-cn // 客户端的语言环境
Accept-Encoding: gzip // 客户端支持的数据压缩格式
请求体: 客户端发给服务器的具体数据,比如文件数据(POST请求才会有)
HTTP通信过程 - 响应
客户端向服务器发送请求,服务器应当做出响应,即返回数据给客户端
HTTP协议规定:1个完整的HTTP响应中包含以下内容
响应头:包含了对服务器的描述、对返回数据的描述
HTTP/1.1 200 OK // 包含了HTTP协议版本、状态码、状态英文名称
Server: Apache-Coyote/1.1 // 服务器的类型
Content-Type: image/jpeg // 返回数据的类型
Content-Length: 56811 // 返回数据的长度
Date: Mon, 23 Jun 2014 12:54:52 GMT // 响应的时间
响应体:服务器返回给客户端的具体数据,比如文件数据
常见的HTTP状态码:
HTTP状态码分类
[图片上传中...(image.png-83129d-1522206422531-0)]
状态码 | 状态码英文名称 | 中文描述 |
---|---|---|
100 | Continue | 继续。客户端应继续其请求 |
101 | Switching Protocols | 切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到HTTP的新版本协议 |
200 | OK | 请求成功。一般用于GET与POST请求 |
201 | Created | 已创建。成功请求并创建了新的资源 |
202 | Accepted | 已接受。已经接受请求,但未处理完成 |
203 | Non-Authoritative Information | 非授权信息。请求成功。但返回的meta信息不在原始的服务器,而是一个副本 |
204 | No Content | 无内容。服务器成功处理,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档 |
205 | Reset Content | 重置内容。服务器处理成功,用户终端(例如:浏览器)应重置文档视图。可通过此返回码清除浏览器的表单域 |
206 | Partial Content | 部分内容。服务器成功处理了部分GET请求 |
300 | Multiple Choices | 多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择 |
301 | Moved Permanently | 永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替 |
302 | Found | 临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI |
303 | See Other | 查看其它地址。与301类似。使用GET和POST请求查看 |
304 | Not Modified | 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源 |
305 | Use Proxy | 使用代理。所请求的资源必须通过代理访问 |
306 | Unused | 已经被废弃的HTTP状态码 |
307 | Temporary Redirect | 临时重定向。与302类似。使用GET请求重定向 |
400 | Bad Request | 客户端请求的语法错误,服务器无法理解 |
401 | Unauthorized | 请求要求用户的身份认证 |
402 | Payment Required | 保留,将来使用 |
403 | Forbidden | 服务器理解请求客户端的请求,但是拒绝执行此请求 |
404 | Not Found | 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面 |
405 | Method Not Allowed | 客户端请求中的方法被禁止 |
406 | Not Acceptable | 服务器无法根据客户端请求的内容特性完成请求 |
407 | Proxy Authentication Required | 请求要求代理的身份认证,与401类似,但请求者应当使用代理进行授权 |
408 | Request Time-out | 服务器等待客户端发送的请求时间过长,超时 |
409 | Conflict | 服务器完成客户端的PUT请求是可能返回此代码,服务器处理请求时发生了冲突 |
410 | Gone | 客户端请求的资源已经不存在。410不同于404,如果资源以前有现在被永久删除了可使用410代码,网站设计人员可通过301代码指定资源的新位置 |
411 | Length Required | 服务器无法处理客户端发送的不带Content-Length的请求信息 |
412 | Precondition Failed | 客户端请求信息的先决条件错误 |
413 | Request Entity Too Large | 由于请求的实体过大,服务器无法处理,因此拒绝请求。为防止客户端的连续请求,服务器可能会关闭连接。如果只是服务器暂时无法处理,则会包含一个Retry-After的响应信息 |
414 | Request-URI Too Large | 请求的URI过长(URI通常为网址),服务器无法处理 |
415 | Unsupported Media Type | 服务器无法处理请求附带的媒体格式 |
416 | Requested range not satisfiable | 客户端请求的范围无效 |
417 | Expectation Failed | 服务器无法满足Expect的请求头信息 |
500 | Internal Server Error | 服务器内部错误,无法完成请求 |
501 | Not Implemented | 服务器不支持请求的功能,无法完成请求 |
502 | Bad Gateway | 充当网关或代理的服务器,从远端服务器接收到了一个无效的请求 |
503 | Service Unavailable | 由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中 |
504 | Gateway Time-out | 充当网关或代理的服务器,未及时从远端服务器获取请求 |
505 | HTTP Version not supported | 服务器不支持请求的HTTP协议的版本,无法完成处理 |
三、HTTP数据安全部分
3.1 数据安全的手段
仅仅用POST请求提交用户的隐私数据,还是不能完全解决安全问题
可以利用软件(比如Charles)设置代理服务器,拦截查看手机的请求数据
因此:提交用户的隐私数据时,一定不要明文提交,要加密处理后再提交
常见的加密算法
MD5 \ SHA \ DES \ 3DES \ RC2和RC4 \ RSA \ IDEA \ DSA \ AES
加密算法的选择
一般公司都会有一套自己的加密方案,按照公司接口文档的规定去加密
什么是MD5
全称是Message Digest Algorithm 5,译为“消息摘要算法第5版”
效果:对输入信息生成唯一的128位散列值(32个字符)
MD5的特点
输入两个不同的明文不会得到相同的输出值
根据输出值,不能得到原始的明文,即其过程不可逆
MD5的应用
由于MD5加密算法具有较好的安全性,而且免费,因此该加密算法被广泛使用
主要运用在数字签名、文件完整性验证以及口令加密等方面
MD5解密网站:http://www.cmd5.com
用户的隐私数据,只有在用户输入那一刻是明文,其他情况都是密文处理
3.2 HTTPS = HTTP + SSL
由于 HTTP 本身不具备加密的功能,所以也无法做到对通信整体(使
用 HTTP 协议通信的请求和响应的内容)进行加密。即,HTTP 报文
使用明文(指未经过加密的报文)方式发送。通信内容在所有的通信线路上都有可能遭到窥视。
窃听.jpg
HTTP 协议中没有加密机制,但可以通过和 SSL(Secure Socket Layer,安全套接层)或TLS(Transport Layer Security,安全层传输协议)的组合使用,加密 HTTP 的通信内容。用 SSL 建立安全通信线路之后,就可以在这条线路上进行 HTTP通信了。与 SSL 组合使用的 HTTP 被称为 HTTPS(HTTPSecure,超文本传输安全协议)或 HTTP over SSL。
3.2.1 HTTPS 是身披 SSL 外壳的 HTTP
通常,HTTP 直接和 TCP 通信。当使用 SSL 时,则演变成先和 SSL 通信,再由 SSL 和 TCP 通信了。简言之,所谓 HTTPS,其实就是身披SSL 协议这层外壳的 HTTP。
HTTPS.jpg
3.2.2 具体的通信步骤
https具体通信步骤.jpg- 步骤 1: 客户端通过发送 Client Hello 报文开始 SSL 通信。报文中包含客户端支持的 SSL 的指定版本、加密组件(Cipher Suite)列表(所使用的加密算法及密钥长度等)。
- 步骤 2: 服务器可进行 SSL 通信时,会以 Server Hello 报文作为应答。和客户端一样,在报文中包含 SSL 版本以及加密组件。服务器的加密组件内容是从接收到的客户端加密组件内筛选出来的。
- 步骤 3: 之后服务器发送 Certificate 报文。报文中包含公开密钥证书。
- 步骤 4: 最后服务器发送 Server Hello Done 报文通知客户端,最初阶段的 SSL 握手协商部分结束。
- 步骤 5: SSL 第一次握手结束之后,客户端以 Client Key Exchange 报文作为回应。报文中包含通信加密中使用的 一种被称为Pre-mastersecret 的随机密码串。该报文已用步骤 3 中的公开密钥进行加密。
步骤 6: 接着客户端继续发送 Change Cipher Spec 报文。该报文会提示服务器,在此报文之后的通信会采用 Pre-master secret 密钥加密。
步骤 7: 客户端发送 Finished 报文。该报文包含连接至今全部报文的整体校验值。这次握手协商是否能够成功,要以服务器是否能够正确解密该报文作为判定标准。
步骤 8: 服务器同样发送 Change Cipher Spec 报文。
步骤 9: 服务器同样发送 Finished 报文。
步骤 10: 服务器和客户端的 Finished 报文交换完毕之后,SSL 连接就算建立完成。当然,通信会受到 SSL 的保护。从此处开始进行应用层协议的通信,即发送 HTTP 请求。
步骤 11: 应用层协议通信,即发送 HTTP 响应。
步骤 12: 最后由客户端断开连接。断开连接时,发送close_notify 报文。上图做了一些省略,这步之后再发送 TCP FIN 报文来关闭与 TCP的通信。
3 3.2.3 关于证书
HTTPS通信图解2
为方便大家理解这个这个图,拓展一步
image.png
数字证书认证机构处于客户端与服务器双方都可信赖的第三方机构的
立场上。 威瑞信( VeriSign) 就是其中一家非常有名的数字证书认证
机构。 我们来介绍一下数字证书认证机构的业务流程。
- 服务器的运营人员向数字证书认证机构提出公开密钥的申请。 数字证书认证机构在判明提出申请者的身份之后, 会对已申请的公开密钥做数字签名, 然后分配这个已签名的公开密钥, 并将该公开密钥放入公钥证书后绑定在一起。
- 服务器会将这份由数字证书认证机构颁发的公钥证书发送给客户端,以进行公开密钥加密方式通信。 公钥证书也可叫做数字证书或直接称为证书。
- 接到证书的客户端可使用数字证书认证机构的公开密钥, 对那张证书上的数字签名进行验证, 一旦验证通过, 客户端便可明确两件事:一, 认证服务器的公开密钥的是真实有效的数字证书认证机构。 二,服务器的公开密钥是值得信赖的。
此处认证机关的公开密钥必须安全地转交给客户端。 使用通信方式时, 如何安全转交是一件很困难的事, 因此, 多数浏览器开发商发布版本时, 会事先在内部植入常用认证机构的公开密钥,其中MAC电脑和iphone都是由系统的钥匙串工具进行统一管理。
3.2.3.1 可证明组织真实性的EV SSL证书
证书的作用是:1.判断通信一方的服务器是否规范 2.证明服务器背后的运营的企业是否真实存在。
所以只有具备以上特性的证书才是EV SSL(Extended Validation SSL Certificate)证书。
典型持有EV SSL证书的web网站将如下图,绿色加锁。此举主要是为了防止钓鱼网站。
image.png
3.2.3.2 用于确认客户端的证书
当然HTTPS还可以有客户端证书。以客户端证书来对客户端进行认证。以便于服务器确认正在通信的客户端是预料中的客户端。
但是客户端证书存在着一下问题点:(市面上只有网银会采用客户端证书,U盾实质是 用于网上银行电子签名及数字认证的工具,其通过独特的技术对网上数据进行加 密、解密及数字签名,以保证网上交易的保密性、真实性、完整性和不可否认性。)
- 想获取证书是,用户得自行安装客户端证书。而客户端证书需要付费购买,且每张证书对应每位用户也就是说需要支付和用户数对等的费用。代价昂贵。
- 让知识层面不同的用户们自行安装证书,这件事情也充满各种挑战。
3.2.3.2 认证机构
SSL机制中介入认证机构之所以可行, 是因为建立在其信用绝对可靠这一大前提下的。 然而, 2011 年 7 月, 荷兰的一家名叫DigiNotar 的认证机构曾遭黑客不法入侵, 颁布了 google.com 和twitter.com 等网站的伪造证书事件。 这一事件从根本上撼动了SSL的可信度。
因为伪造证书上有正规认证机构的数字签名, 所以浏览器会判定
该证书是正当的。 当伪造的证书被用做服务器伪装之时, 用户根
本无法察觉到
3.2.3.3 子认证机构颁发的证书称为自签名证书
如果使用 OpenSSL这套开源程序, 每个人都可以构建一套属于自己的认证机构, 从而自己给自己颁发服务器证书。 但该服务器证书在互网上不可作为证书使用, 似乎没什么帮助。独立构建的认证机构叫做认证机构, 由自认证机构颁发的“无用”证书也被戏称为自签名证书。浏览器访问该服务器时, 会显示“无法确认连接安全性”或“该网站的安全证书存在问题”等警告信息。
3.2.3.4 客户端验证过程简析
所以从上图可以看出,服务器公开密钥的证书包含公开密钥和CA签名,首先客户端通过系统或者浏览器中存在CA根证书,来验证该证书的可靠性。然后通过系统或者浏览器中存在该CA的公开密钥进行验证对应的CA签名,验证了证书的有效性之后,取出服务器的公开密钥。
3.2.3.5 证书的内容
X.509 应该是比较流行的 SSL 数字证书标准,包含(但不限于)以下的字段:
image.png
比如京东的证书:
image.png
image.png
image.png
3.2.3.6 数字证书的生成及验证
数字证书的生成是分层级的,下一级的证书需要其上一级证书的私钥签名。
所以后者是前者的证书颁发者,也就是说上一级证书的 Subject Name 是其下一级证书的 Issuer Name。
在得到证书申请者的一些必要信息(对象名称,公钥私钥)之后,证书颁发者通过 SHA-256 哈希得到证书内容的摘要,再用自己的私钥给这份摘要加密,得到数字签名。综合已有的信息,生成分别包含公钥和私钥的两个证书。
扯到这里,就有几个问题:
问:如果说发布一个数字证书必须要有上一级证书的私钥加密,那么最顶端的证书——根证书怎么来的?
根证书是自签名的,即用自己的私钥签名,不需要其他证书的私钥来生成签名。
问:怎么验证证书是有没被篡改?
131094075-787b7f5dcaf42b67当客户端走 HTTPS 访问站点时,服务器会返回整个证书链。以下图的证书链为例:
要验证
*.wikipedia.org
这个证书有没被篡改,就要用到GlobalSign Organization Validation CA - SHA256 - G2
提供的公钥解密前者的签名得到摘要 Digest1,我们的客户端也计算前者证书的内容得到摘要 Digest2。对比这两个摘要就能知道前者是否被篡改。后者同理,使用GlobalSign Root CA
提供的公钥验证。当验证到到受信任的根证书时,就能确定*.wikipedia.org
这个证书是可信的。
问:为什么上面那个根证书 GlobalSign Root CA
是受信任的?
数字证书认证机构(Certificate Authority, CA)签署和管理的 CA 根证书,会被纳入到你的浏览器和操作系统的可信证书列表中,并由这个列表判断根证书是否可信。所以不要随便导入奇奇怪怪的根证书到你的操作系统中。
问:生成的数字证书(如 *.wikipedia.org
)都可用来签署新的证书吗?
141094075-72e8b7480c7adf05不一定。如下图,拓展字段里面有个叫 Basic Constraints 的数据结构,里面有个字段叫路径长度约束(Path Length Constraint),表明了该证书能继续签署 CA 子证书的深度,这里为0,说明这个
GlobalSign Organization Validation CA - SHA256 - G2
只能签署客户端证书,而客户端证书不能用于签署新的证书,CA 子证书才能这么做。
3.2.3.7 iOS 上对证书链的验证
在 Overriding TLS Chain Validation Correctly 中提到:
When a TLS certificate is verified, the operating system verifies its chain of trust. If that chain of trust contains only valid certificates and ends at a known (trusted) anchor certificate, then the certificate is considered valid.
所以在 iOS 中,证书是否有效的标准是:
信任链中如果只含有有效证书并且以可信锚点(trusted anchor)结尾,那么这个证书就被认为是有效的。
其中可信锚点指的是系统隐式信任的证书,通常是包括在系统中的 CA 根证书。不过你也可以在验证证书链时,设置自定义的证书作为可信的锚点。
3.2.4 TCP层面的具体通信流程
timg.jpeg四、iOS层面的SSL层面的实现
4.1 NSURLSession 实现 HTTPS
具体到使用 NSURLSession 走 HTTPS 访问网站,-URLSession:didReceiveChallenge:completionHandler: 回调中会收到一个 challenge,也就是质询,需要你提供认证信息才能完成连接。这时候可以通过 challenge.protectionSpace.authenticationMethod 取得保护空间要求我们认证的方式,如果这个值是 NSURLAuthenticationMethodServerTrust 的话,我们就可以插手 TLS 握手中“验证数字证书有效性”这一步。
4.2 默认的实现
系统的默认实现(也即代理不实现这个方法)是验证这个信任链,结果是有效的话则根据 serverTrust 创建 credential 用于同服务端确立 SSL 连接。否则会得到 “The certificate for this server is invalid…” 这样的错误而无法访问。
4.3 自定义实现
4.3.1 -(void)URLSession:(NSURLSession *)session
didReceiveChallenge 代理说明
如果我们要实现这个代理方法的话,需要提供 NSURLSessionAuthChallengeDisposition(处置方式)和 NSURLCredential(资格认证)这两个参数给 completionHandler 这个 block:
-(void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition,
NSURLCredential * _Nullable))completionHandler {
// 如果使用默认的处置方式,那么 credential 就会被忽略
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
NSURLCredential *credential = nil;
if ([challenge.protectionSpace.authenticationMethod
isEqualToString:
NSURLAuthenticationMethodServerTrust]) {
/* 调用自定义的验证过程 */
if ([self myCustomValidation:challenge]) {
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
if (credential) {
disposition = NSURLSessionAuthChallengeUseCredential;
}
} else {
/* 无效的话,取消 */
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge
}
}
if (completionHandler) {
completionHandler(disposition, credential);
}
}
在 [self myCustomValidation:challenge] 调用自定义验证过程,结果是有效的话才创建 credential 确立连接。
自定义的验证过程,需要先拿出一个 SecTrustRef 对象,它是一种执行信任链验证的抽象实体,包含着验证策略(SecPolicyRef)以及一系列受信任的锚点证书,而我们能做的也是修改这两样东西而已。
SecTrustRef trust = challenge.protectionSpace.serverTrust;
拿到 trust 对象之后,可以用下面这个函数对它进行验证。
static BOOL serverTrustIsVaild(SecTrustRef trust) {
BOOL allowConnection = NO;
// 假设验证结果是无效的
SecTrustResultType trustResult = kSecTrustResultInvalid;
// 函数的内部递归地从叶节点证书到根证书的验证
OSStatus statue = SecTrustEvaluate(trust, &trustResult);
if (statue == noErr) {
// kSecTrustResultUnspecified: 系统隐式地信任这个证书
// kSecTrustResultProceed: 用户加入自己的信任锚点,显式地告诉系统这个证书是值得信任的
allowConnection = (trustResult == kSecTrustResultProceed
|| trustResult == kSecTrustResultUnspecified);
}
return allowConnection;
}
4.2.2 域名验证
可以通过以下的代码获得当前的验证策略:
CFArrayRef policiesRef;
SecTrustCopyPolicies(trust, &policiesRef);
打印 policiesRef 后,你会发现默认的验证策略就包含了域名验证,即“服务器证书上的域名和请求域名是否匹配”。如果你的一个证书需要用来连接不同域名的主机,或者你直接用 IP 地址去连接,那么你可以重设验证策略以忽略域名验证:
NSMutableArray *policies = [NSMutableArray array];
// BasicX509 不验证域名是否相同
SecPolicyRef policy = SecPolicyCreateBasicX509();
[policies addObject:(__bridge_transfer id)policy];
然后再调用 serverTrustIsVaild() 验证。
但是如果不验证域名的话,安全性就会大打折扣
4.2.3 自签名的证书链验证
在 App 中想要防止中间人攻击,比较好的做法是将公钥证书打包进 App 中,然后在收到服务端证书链的时候,能够有效地验证服务端是否可信,这也是验证自签名的证书链所必须做的。
假设你的服务器返回:[你的自签名的根证书] — [你的二级证书] — [你的客户端证书],系统是不信任这个三个证书的。
所以你在验证的时候需要将这三个的其中一个设置为锚点证书,当然,多个也行。
比如将 [你的二级证书] 作为锚点后,SecTrustEvaluate() 函数只要验证到 [你的客户端证书] 确实是由 [你的二级证书] 签署的,那么验证结果为 kSecTrustResultUnspecified,表明了 [你的客户端证书] 是可信的。下面是设置锚点证书的做法:
NSMutableArray *certificates = [NSMutableArray array];
NSDate *cerData = /* 在 App Bundle 中你用来做锚点的证书数据,证书是 CER 编码的,常见扩展名有:cer, crt...*/
SecCertificateRef cerRef = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)cerData);
[certificates addObject:(__bridge_transfer id)cerRef];
// 设置锚点证书。
SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)certificates);
只调用 SecTrustSetAnchorCertificates () 这个函数的话,那么就只有作为参数被传入的证书作为锚点证书,连系统本身信任的 CA 证书不能作为锚点验证证书链。要想恢复系统中 CA 证书作为锚点的功能,还要再调用下面这个函数:
// true 代表仅被传入的证书作为锚点,false 允许系统 CA 证书也作为锚点
SecTrustSetAnchorCertificatesOnly(trust, false);
这样,再调用 serverTrustIsVaild() 验证证书有效性就能成功了。
4.2.4 APP里面打包证书
对于一些校验更加严格的应用,有时需要更加严格的https校验。因此服务器会事先把证书给到客户端,客户端在进行https通信或者更应该说是ssl通信的时候(tcp)层面时候,需要操作如下:
-
step1: 从服务器工程师中获取我们公司服务器下发给对应项目的证书,如下图我们需要使用的二级证书(有些可能是三级证书)。
670BBEDF-7B62-4DA3-A1C3-43A50CA4812A.png -
step2:将该证书生成.cer的格式,IOS端或者AFNetworking只支持cer的格式
方法很多种,比如向命令行中输入(MAC电脑上钥匙串工具也不失为一个好的选择):
openssl x509 -in 你的证书.crt -out 你的证书.cer -outform der
-
step3:将证书拖入工程中,注意一定要勾选“Add to targets”这样在 [NSBundle mainBundle]才能找得到证书。
47157233-5C8C-4E33-AB18-294C3E765255.png -
step4: 根据客户端证书来验证服务器证书的有效性
4.2.4.1 TCP层面的SSL验证(这里基于GCDAsyncSocket为例)
如果需要进行SSL通信,那么再代理里面,调用startTLS: 函数
-(void) socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port{
if (useSSL) {
if (self.sslHandlerBlock) {
NSDictionary* dict = self.sslHandlerBlock();
[_tcpSocket startTLS:dict];
}else{
[_tcpSocket startTLS:nil];
}
}else{
#if ReadFrame
//开始使能接收
[_tcpSocket readDataToLength:HeadNoHiddenLen withTimeout:-1 tag:HeadNoHiddenTag];
#else
[_tcpSocket readDataWithTimeout:-1 tag:-1];
#endif
}
if ([self.remoteServiceDelegate respondsToSelector:@selector(socket:didConnectToHost:port:)]) {
[self.remoteServiceDelegate socket:sock didConnectToHost:host port:port];
}
}
其中dictionary里面的内容为:
(lldb) po dict
{
GCDAsyncSocketManuallyEvaluateTrust = 1;
kCFStreamSSLPeerName = "bolai-test.yunext.com";
}
具体的可以查看startTLS: 函数的说明,其中GCDAsyncSocketManuallyEvaluateTrust 为YES是为了其能调用以下代理方法,告知我们需要自己参与证书校验。
kCFStreamSSLPeerName: 服务于域名校验
- (void)socket:(GCDAsyncSocket *)sock didReceiveTrust:(SecTrustRef)trust
completionHandler:(void (^)(BOOL shouldTrustPeer))completionHandler
- (void)socket:(GCDAsyncSocket *)sock didReceiveTrust: 里面的函数处理
其中线程方面特别注意,注意阅读该函数的使用,在此不再赘述。
- (void)socket:(GCDAsyncSocket *)sock didReceiveTrust:(SecTrustRef)trust
completionHandler:(void (^)(BOOL shouldTrustPeer))completionHandler
{
/**************************************************************
该方法中需要特别注意,对于证书的验证有一个方法会阻塞线程,所以这里需要单独处理
**************************************************************/
NSOperationQueue* queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
BOOL isValid = NO;
isValid = [SSLTool evaluateServerTrust:trust];
completionHandler(isValid);
}];
}
+ (BOOL)evaluateServerTrust:(SecTrustRef)trust
{
NSMutableArray *policies = [NSMutableArray array];
//需要校验域名 AppBalanceHost 是一个定义好的宏
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)AppBalanceHost)];
SecTrustSetPolicies(trust, (__bridge CFArrayRef)policies);
NSMutableArray *pinnedCertificates = [NSMutableArray array];
//[self defaultPinnedCertificates] bundle里面的cer证书的 数据
for (NSData *certificateData in [self defaultPinnedCertificates]) {
[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
}
//设置证书锚点
SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)pinnedCertificates);
// 验证该证书是否有效
if (!SSLServerTrustIsValid(trust)) {
return NO;
}
// obtain the chain after being validated, which *should* contain the pinned certificate in the last position (if it's the Root CA)
// 将服务器返回的证书链,转化为证书列表
NSArray *serverCertificates = SSLCertificateTrustChainForServerTrust(trust);
//判断服务器返回证书是否在本地的 bundle列表(即APP打包进来客户端可以相信的证书列表)
for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
if ([[self defaultPinnedCertificates] containsObject:trustChainCertificate]) {
return YES;
}
}
return NO;
}
static BOOL SSLServerTrustIsValid(SecTrustRef serverTrust) {
BOOL isValid = NO;
SecTrustResultType result;
__Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out);
isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
_out:
return isValid;
}
static NSArray * SSLCertificateTrustChainForServerTrust(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];
}
4.2.5 HTTPS通信的实现
自定义实现证书授信,前文已经进行了详尽的描述,这里讲解下AFNetworking的实现
+ (void)inithttps:(AFHTTPSessionManager *)mgr{
// allowInvalidCertificates 是否允许无效证书(也就是自建的证书),默认为NO(如果采用自签名证书请改为YES)
//validatesDomainName 是否进行域名校验,建议都为YES
//pinnedCertificates 如果需要采用APP打包近来的证书校验,请将.cer的数据包装成NSMutableSet并且赋值
// policyWithPinningMode: 方法中3个参数的含义见下面
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
securityPolicy.allowInvalidCertificates = NO;
securityPolicy.validatesDomainName = YES;
// securityPolicy.pinnedCertificates = cerSet;
mgr.securityPolicy = securityPolicy;
}
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
AFSSLPinningModeNone,
AFSSLPinningModePublicKey,
AFSSLPinningModeCertificate,
};
AFSSLPinningModeNone:
这个模式表示不做SSL pinning,只跟浏览器一样在系统的信任机构列表里验证服务端返回的证书。若证书是信任机构签发的就会通过,若是自己服务器生成的证书就不会通过。
AFSSLPinningModePublicKey:
代表客户端会将服务器端返回的证书与本地保存的证书中,PublicKey的部分进行校验;如果正确,才继续进行。
AFSSLPinningModeCertificate: 代表客户端会将服务器端返回的证书和本地保存的证书中的所有内容,包括PublicKey和证书部分,全部进行校验;如果正确,才继续进行。
4.2.6 常见证书调试观察服务器的返回证书结果
服务器推送过来的证书,包含着两级,一级为GlobalSign Domain Validation CA - SHA256 - G 其父证书为 GlobalSign Root CA
二级为通配域名证书(^.yunext.com i)的证书,该证书是父节点证书GlobalSign Domain Validation CA - SHA256 - G
如果是自签名的证书调试结果如下:
该证书的父节点证书还是自身,没有可信的父节点证书。
该结果的result的返回 kSecTrustResultFatalTrustFailure
采用 Let’s Encrypt 进行免费签名https证书(典型的中级认证家机构,有可能会被当成自认证证书,当然从其官网可以看出目前和他的合作机构是比较多的)
image.png image.png参考书籍
《计算机网络自顶向下方法》
《图解HTTP》 -- 上野 宣【日】
《图解TCP、IP》
参考链接
GCDAsyncSocket two way authentication SSL 双向认证
//典型的应用的blog
https://www.cnblogs.com/whoislcj/p/6369717.html
iOS 中对 HTTPS 证书链的验证【推荐】
GCDAsyncSocket的使用说明链接
https://github.com/robbiehanson/CocoaAsyncSocket/wiki/Reference_GCDAsyncSocket
AFNetworking的使用说明链接
http://cocoadocs.org/docsets/AFNetworking/3.1.0/
从SSL安全传输到iOS证书安全体系1
从SSL安全传输到iOS证书安全体系2
SSL工作原理
AFNetworking解析(四)
AFNetworking 概述(一)
网友评论