聊聊 App Transport Security 和 HTTPS

07/12/2016

App Transport Security

苹果总是成为引领行业的企业,即使很多产品和功能别人早就做出来了,苹果也能靠自己惊人的庞大体量和宣传效果让它火起来。去年,苹果在 iOS 9 和 OS X El Capitan 中引入了 ATS,顾名思义,这项功能屏蔽了所有 HTTP 访问,并保护用户的 HTTPS 访问。

由于这只是 app 中一项起到保护作用的功能,多数开发者一般选择关闭这项功能(下文中会提到)。可能是苹果的责任心爆发,他们要求开发者在 2017 年 1 月 1 日后提交的APP中必须启用 ATS,换句话说,以后的 iOS 应用必须采用 HTTPS 访问,不仅是数据抓取,即使是 web view 访问也必须是 HTTPS 的。

HTTPS

请写出专业名词全称(2 分):

HTTP: Hypertext Transfer Protocol

HTTPS: Hypertext Transfer Protocol Secure

SSL: Secure Sockets Layer

TLS: Transport Layer Security

CA: Certificate Authority

把上面的这些名词串在一起就可以解释 HTTPS 了。它是在 HTTP 的基础上进行加密的协议,而 SSL/TLS (在这里就不解释这二者了,它们可以简单地看做同一个东西在不同阶段的名字)在传输层上完成的就是加密的任务。

HTTPS 的工作原理广义上类似于人的社交关系。用火车站检票举例,火车站信任检票员可以胜任自己的工作,他们负责检查你的票和身份证是否和本人相符。如果你提供的信息是正确的,那么你将借由检票员获得火车站的信任,并允许你乘车。

回到 HTTPS,你信任你的浏览器通过可信的证书提供商 CA 进行验证,浏览器检查网站证书与网站内容是否相符,通过这层验证表示你通过你的浏览器信任了这个 HTTPS 网站。

在这之前可以耍的小聪明

ATS 强制使用 HTTPS,对于遍地 HTTP 的中国互联网环境来说,是个坏消息。虽然 HTTP 和 HTTPS 在迁移上并不复杂,但是一个好用的且可用于商业用途的HTTPS 证书价格不菲,这导致开发者一般都选择屏蔽这个功能。

在 info.plist 文件中,新建一个键为 NSAppTransportSecurity,在其中新建 Boolean 型的键 NSAllowsArbitraryLoads 设置为 YES。

访问 HTTP 网页报错

为了模拟这种情况,创建一个新工程,并添加了一个 webView,加入如下代码:

[self.webView loadRequest:[[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"http://www.qq.com/"]]];

运行后,不会加载 QQ 的主页,同时会收到访问被禁止的 log,这表示 ATS 完全屏蔽了 HTTP 的访问。

访问 HTTPS 网页报错(-9802)

将上述的代码域名改为 https://www.baidu.com/ 后,得到了报错。

为什么我们访问了HTTPS协议的网站却依然报错呢?这里我们必须参考 NSAppTransportSecurity 的官方文档查找答案。

根据文档,ATS 不仅要求请求必须是 HTTPS 协议的,还有其他的 3 点要求:

1.TLS 协议的版本必须在 TLS1.2 以上

2.要求支持完全正向保密(Perfect Forward Privacy)的加密方式,列举如下:

  • TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
  • TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
  • TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
  • TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
  • TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
  • TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
  • TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
  • TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
  • TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
  • TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
  • TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA

3.签名算法至少使用一个 SHA256 指纹和一个高于 2048 位的 RSA(Rivest-Shamir-Adleman) 或高于 256 位的 ECC(Elliptic-Curve Cryptography) 秘钥

检查百度的证书,发现它没有符合上述的第二个条件,引起了 ATS 的报错。

AFNetWorking + HTTPS(-1012)

作为 iOS/OS X 平台上最好的网络访问库,AFNetWorking 本身是支持 HTTPS 访问的,而且对于 HTTPS 的要求在某些方面甚至高于 ATS 本身。

由于私人开发者一般不愿意购买权威 CA 颁发的证书,所以 AFNetWorking 会判断我们的证书是非法的,这时我们有两种方法选择。

1. 让 AFNetWorking 完全不检测证书

早期的 AFNetWorking 中,我们可以通过引用的 pch(Precompile prefix header),设置一下 AFNetWorking 的配置:

#import <SystemConfiguration/SystemConfiguration.h>
#import <MobileCoreServices/MobileCoreServices.h>
#define AFNETWORKING_ALLOW_INVALID_SSL_CERTIFICATES

后来,这种宏的设定失效了,于是我们可以修改库的安全政策:

AFSecurityPolicy *securityPolicy = [AFSecurityPolicy defaultPolicy];
securityPolicy.allowInvalidCertificates = YES;

另外,由于域名审核也是一个非常麻烦的过程,有的开发者还没有自己的域名,这时还需要设置不匹配域名:

securityPolicy.validatesDomainName = NO;

2. 让 AFNetworking 检测本地提供的个人证书

这种方法则是把自己当做一个 CA,自己一定是信任自己的,由此检查访问的域名是否匹配。

由于库要求检查 cer 文件,如果你手中的证书是 pem 文件则可以进行转换:

openssl x509 -in <servercertification>.pem -outform der -out server.cer

然后设置安全策略:

AFSecurityPolicy *securityPolicy = [AFSecurityPolicy AFSSLPinningModeCertificate];

AFNetworking 自动检查 bundle 当中的证书,这种方式的安全性相比第一种高了一些。

定制 ATS

这里列举的一些方式可能会在未来必须采用ATS后失效,但在目前是可用的。

1. 设置不使用 ATS 的域

在 info.plist 的 ATS 部分中的 NSExceptionDomains 中添加域名,并在其中将 NSIncludesSubdomains 和 NSExceptionsAllowsInsecureHTTPLoads 均设为 YES。

2. 设置使用 ATS 的域

首先按照上面的方法将 NSAllowsArbitraryLoads 设为 YES,并在 NSExceptionDomains 中添加域名,把 NSExceptionsAllowsInsecureHTTPLoads 置为 NO。

3. 降低 ATS 要求

同样在 NSExceptionDomains 中添加域名。如果需要降低 TLS 版本则修改 NSExceptionMinimumTLSVersion 为 TLSv1.1 或 TLSv1.0。如果不支持正向保密,则设置NSExceptionRequiresForwardSecrecy 为 NO。

小结

ATS 作为苹果保证用户安全地访问提供的一项功能,填补了在移动端用户不能了解自己是否访问了加密的站点的盲区。对这一功能的强行要求,可能会加强开发者甚至是用户对网络安全的重视。但即使如此,开发者也不能对安全问题掉以轻心,反而在关键服务上应该更加小心。