转载:写给iOS开发者看的HTTPS指南
发布日期:2017-01-06苹果在 WWDC 2016 上宣布:2016 年底将要求所有 APP 适配苹果的 App Transport Security,简单地说就是除了特殊情况(浏览器、第叁方服务、媒体)外,APP 跟服务端的通信必须使用 HTTPS 协议,否则 iOS 9 和 macOS 10.11 起,操作系统将有能力阻止所有的明文 HTTP 请求。在上面的 session 中,苹果还对具体的细节做出了要求。不过,前日,苹果宣布将这个 deadline 无限期推迟。
本文将着重以大部分iOS开发者能理解的方式介绍APP启用HTTPS支持的过程中跟APP相关的部分,剩余的协议细节将一笔带过。
HTTPS 基础
定义
HTTPS 看似跟 HTTP 一样,其实它只是看起来跟 HTTP 一样,实际上是一种新的网络架构。在当前情况下,HTTPS 的英文全称应该是 HTTP over TLS。
HTTPS 请求和HTTP请求的异同
普通HTTP请求直接基于TCP,在互联网上明文传播,而且没有任何校验,链路上的每一个节点都可以对数据包进行篡改,使用手机网络访问HTTP网站被插入流量球甚至广告等运营商劫持行为就是最常见的例子。而HTTPS请求运行在TLS 层之上,TLS运行在TCP上,TLS 有独特的握手、建立连接、数据验证机制,让运行商劫持无处下手:只要任何一个数据包被篡改,数据校验就会失败,这个请求会客户端直接抛弃,网页不会显示。当我们用HTTP协议来解释TLS层携带的内容时,这个东西就被称为HTTPS 啦。
HTTP协议简析
HTTP 协议是一个非常简单而强壮的协议,它规定了以文本方式解析数据后哪一部分该代表什么:头部携带特定信息,正文部分被渲染为网页。所以,任何数据都可以被 HTTP 协议解析,无论他是基于 TCP 还是 TLS 传输,或者只是硬盘上的一个文件。
请注意,此处的 HTTP 协议和上一小节中的 HTTP 请求是两个概念。
HTTPS证书
证书是什么
下面两张图分别是我的个人博客的一张旧证书的 cer 和 crt 两种格式,在 Finder 中点击空格预览的结果:
下面是 crt 格式的证书内容:
证书就是使用特殊格式加密的一段字符串,可以被读取并拿出关键信息。iOS 中 NSURLSession 验证的就是 cer 格式的这个证书。
证书周边知识
下面是一个HTTPS证书典型的购买、部署流程:
1、在*UNIX 环境下使用openssl 工具生成一对一匹配的 私钥 和 CERTIFICATE REQUEST 文件(以 —–BEGIN CERTIFICATE REQUEST—– 开头)。私钥为绝密,绝对不能泄露,最好在生产服务器直接生成,这样就不需要网络传输,更加安全。
2、将CERTIFICATE REQUEST 文件提交到证书服务机构CA,服务机构根据证书级别进行 域名认证、公司认证、安全认证等不同级别的安全验证。
3、验证通过后,服务机构将基于我们提交的 CERTIFICATE REQUEST 内容,使用他已有的证书派生出子证书,提供给我们下载。
4、我们拿到服务机构颁发的两个 crt 格式的证书(root 证书 及我们的域名证书),再配合本地的私钥,到 Apache、Nginx 等 web server 上部署,部署时会验证“私钥”是否和“域名证书”匹配。
5、用户在以HTTPS 协议访问网站时,浏览器会进行如下几步安全验证:
·域名证书中的域名和实际域名是否一致;
·域名证书和 root 证书是否匹配;
·root 证书是否可信;
需要注意的点
1、在某些低安全级别证书申请中(如仅验证域名所有权的证书),私钥可以让签发服务器代为生成,但这样做有一定的安全风险。
2、root证书也可以在我们本机生成,如 12306 的自签名证书,但这样不会被普通浏览器信任。
3、私钥为绝密,因为证书全部都是公开的,任何人都可以提取,如果私钥被别人获取,被部署到别人的服务器中,那所有人就会认为那台服务器是完全合法的。这已经不是中间人攻击了,这时候他就是你。
NSURLSession 对证书的验证
证书验证方法
在URLSessionDelegate 中有一个方法 func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) 专门用来处理 HTTPS 证书的处理。具体操作可以参考 Pitaya 中的代码。
我简单复述一下处理流程:
1、发现是 HTTPS 请求,取出证书
2、进入证书处理流程:
(1)本地证书分两种情况:① 本地存储着证书服务机构颁发的 crt 文件转换而来的 cer 文件,使用 NSData 进行内容对比 ② 本地存储着自签名的 cer 格式的证书,使用 NSData 进行内容对比;
(2)匹配成功,手动让请求继续,这一步可以让自签名证书绕过 iOS 系统的证书合法性验证;
(3)匹配失败,进入错误处理流程;
(4)如果自签名证书不做手动处理,那么在这个方法结束后链接就会被系统关闭,因为 root 证书不合法。
所以,在APP 提交的时候,苹果会检查是否将 ATS 配置为了“全部无脑通过”,这种操作是被禁止的。当然,苹果也可以在系统层面一刀切,但是那样得挂多少个 APP 啊,苹果不会那么做。所以会被影响的应该只是新提交的 ipa。
SSL 钢钉原理
如果有人通过一些手段通过了域名所有权认证(非常容易,域名邮箱、DNS指向甚至在根目录放一个文件都可以验证通过),拿到了一个合法的对应你的域名的 HTTPS 证书,这时候他在广场开放了一个没有密码的 wifi,命名为 CMCC,这样,几乎所有的开着 wifi 的手机都会自动连接,这时候他只需要做一个简单的 DNS 劫持,就可以把所有应该向你网站发送的需求劫持到他那里。
但是:如果你做了 SSL 钢钉验证,那么他的证书就不会被验证通过。他也没法用你的证书启动服务,因为私钥和证书的验证是 SSL 协议强制做的,他没有你的私钥,他的 web server 就没法启动。所以,私钥千万不能泄露。
关于自签名证书
自签名证书不会被浏览器信任,因为每次有新的 HTTPS 证书到达某个操作系统时,系统会去访问 root 证书的服务器以确定域名证书的身份,这些合法的 root 证书服务商就是固定的那几家,显然自签名证书不会被信任,所以我们在 12306 抢票的时候需要先下载他的自签名证书的 root 证书并手动信任,不然就打不开页面。
我们可以看到,自签名证书的验证在代码层面,在审核的时候是完全不可感知的,所以就没有什么“苹果不接受自签名证书”之类的问题了。而且,自签名证书被广泛的用于各种系统内部的连接加密,不是苹果可以一刀切的:如果粗暴的在操作系统层面阻止了自签名证书,导致企业客户的系统突然挂掉,后果不可想象。
关于证书更换
证书都有有效期,在过期之前需要申请新证书,这时候 SSL 钢钉该怎么处理呢?动态下发当然是不行的,为什么要验证证书?就是因为网络不可信任。Pitaya 前两天加入了这个逻辑:在新证书和旧证书交接的一段时间内,上线新版本,同时包含新旧证书,这样可以保证更新过的用户可以对证书更换无感。
另外,设置 SSL 钢钉不适合面向普通用户的 APP,因为总是有人万年不更新,这更适合企业内部 APP,可以通过行政手段及自驱力(业绩啊,提成啊)推动更新。
关于所谓的双向验证
感觉这里是大家误解最大的地方:大部分人所谓的“双向验证”就是自签名证书的验证并手动继续而已。
HTTPS 是支持双向认证的,不过那指的是客户端(浏览器或 APP)也像服务端一样,在发送请求给服务端的时候带上证书,再由服务端使用对应的私钥进行验证。一般 APP 不需要这么做。
苹果的要求
后端
前两条拿给后端看就行,第3条用 Nginx 也很容易实现。最后一条 Exception 就是苹果马上就不支持的。
iOS 端
使用这些 API 可以单独绕过。
流媒体文件可以添加例外,WKWebView 可以直接设置为绕过。
总结
购买的证书:什么都不用管,改一下服务器地址就行,代码完全不用改。
自签名证书:找后端哥们儿要一个 crt,自己提取也行,转换成 cer ,放到相应的方法里,手动让处理流程继续即可。
文章来源:autolayout.club