学习文章
概念释疑
为了强制增强数据访问安全,iOS9
默认会把所有从 NSURLConnection
、 CFURL
、 NSURLSession
发出的 HTTP
请求,都改为 HTTPS
请求:iOS9.x-SDK编译时,默认会让所有从NSURLConnection
、 CFURL
、 NSURLSession
发出的 HTTP
请求统一采用TLS 1.2
协议。因为 AFNetworking
现在的版本底层使用了 NSURLConnection
,众多App将被影响(基于iOS8.x-SDK的App不受影响)。服务器因此需要更新,以解析相关数据。而这一做法,官方文档称为ATS,全称为App Transport Security
,是iOS9的一个新特性。如不更新,可通过在 Info.plist
中声明,倒退回不安全的网络请求。
一个符合 ATS 要求的 HTTPS,应该满足如下条件:
- Transport Layer Security协议版本要求TLS1.2以上
- 服务的Ciphers配置要求支持Forward Secrecy等
- 证书签名算法符合ATS要求等
注: ATS只信任知名CA颁发的证书,小公司所使用的 self signed certificate,还是会被ATS拦截。
什么是SSL/TLS?跟HTTP和HTTPS有什么关系?
什么是SSL/TLS? SSL你一定知道,在此不做赘述。主要说下什么是TLS,还有跟HTTP和HTTPS有什么关系。
TLS 是 SSL 新的别称:
“TLS1.0”之于“SSL3.1”,犹“公元2015”之于“民国104”,“一千克”之于“一公斤”:称呼不同,意思相同。
SSL 3.0版本之后的迭代版本被重新命名为TLS 1.0:TLS 1.0=SSL 3.1。所以我们平常也经常见到 “SSL/TLS” 这种说法。
目前,应用最广泛的是TLS 1.0,接下来是SSL 3.0。目前主流浏览器都已经实现了TLS 1.2的支持。
常用的有下面这些:
- SSL 2.0
- SSL 3.0
- TLS 1.0 (SSL 3.1)
- TLS 1.1 (SSL 3.1)
- TLS 1.2 (SSL 3.1)
那为什么标题是“使用HTTPS”而没有提及SSL和TLS什么事? “SSL/TLS”跟HTTP和HTTPS有什么关系?
要理解这个,要看下他们之间的关系:
HTTP+SSL/TLS+TCP = HTTPS
或者
HTTPS = “HTTP over SSL”
也就是说
Apple让你的HTTP采用SSL/TLS协议,就是让你从HTTP转到HTTPS。而这一做法,官方文档称为ATS,全称为App Transport Security。
为什么要用SSL/TLS?
不使用SSL/TLS的HTTP通信,就是不加密的通信!
不使用SSL/TLS的HTTP通信,所有信息明文传播,带来了三大风险:
- 窃听风险(eavesdropping):第三方可以获知通信内容。
- 篡改风险(tampering):第三方可以修改通信内容。
- 冒充风险(pretending):第三方可以冒充他人身份参与通信。
SSL/TLS协议是为了解决这三大风险而设计的,希望达到:
- 所有信息都是加密传播,第三方无法窃听。
- 具有校验机制,一旦被篡改,通信双方会立刻发现。
- 配备身份证书,防止身份被冒充。
SSL/TLS的作用,打个比方来讲:
如果原来的 HTTP 是塑料水管,容易被戳破;那么如今新设计的 HTTPS 就像是在原有的塑料水管之外,再包一层金属水管(SSL/TLS协议)。一来,原有的塑料水管照样运行;二来,用金属加固了之后,不容易被戳破。
解决方案
符合ATS
如果所有请求接口都符合ATS,那么,对于前端来说,则不需要做任何额外工作.
不完全符合ATS
这是很有可能的,因为即使公司的正式接口符合ATS,开发中,还是需要测试接口的.我们很有可能没有时间金钱,使测试接口也符合ATS,也没必要.
对于不完全符合ATS的情况,我们可以通过调整plist来轻松做到.
正如在上图中看到的,苹果官方提供了一些可选配置项来决定是否开启ATS模式.
开发者可以针对某些确定的URL不使用ATS,这需要在工程中的info.plist中标记NSExceptionDomains。在NSExceptionDomains字典中,可以显式的指定一些不使用ATS的URL.键值如下:
- NSIncludesSubdomains
- NSExceptionAllowInsecureHTTPLoads
- NSExceptionRequiresForwardSecrecy
- NSExceptionMinimumTLSVersion
- NSThirdPartyExceptionAllowsInsecureHTTPLoads
- NSThirdPartyExceptionMinimumTLSVersion
- NSThirdPartyExceptionRequiresForwardSecrecy
关于App Transport Security,每个应用都属于4个大类当中的一类。我们来看看每一个大类都是怎样影响应用的。
-- | 分类名 | 解释 |
---|---|---|
1. | HTTPS Only (只有HTTPS,所有情况下都使用ATS) | 如果你的应用只基于支持HTTPS的服务器,那么你太幸运了。你的应用不需要做任何改变。但是,注意App Transport Security要求TLS 1.2而且它要求站点使用支持forward secrecy协议的密码。证书也要求是符合ATS规格的。因此慎重检查与你的应用交互的服务器是不是符合ATS的要求非常重要。 |
2. | Mix & Match(混合) | 你的应用与一个不符合ATS要求的服务器工作是很有可能的。在这种情况下,你需要告诉操作系统哪些站点是涉及到的然后在你的应用的 Info.plist文件中指明哪些要求没有达到。 |
3. | Opt Out(禁用ATS) | 如果你在创建一个网页浏览器,那么你有一个更大的麻烦。因为你不可能知道你的用户将要访问那个网页,你不可能指明这些网页是否支持ATS要求且在HTTPS上传输。在这种情况下,除了全部撤销 App Transport Security 没有其它办法。 |
4. | Opt Out With Exceptions(除特殊情况外,都不使用ATS) | 当你的应用撤消了App Transport Security,,但同时定义了一些例外。这非常有用就是当你的应用从很多的服务器上取数据,但是也要与一个你可控的API交互。在这种情况下,在应用的Info.plist文件中指定任何加载都是被允许的,但是你也指定了一个或多个例外来表明哪些是必须要求 App Transport Security的。 |
根据上表,分别做一下解释
1.HTTPS Only (只有HTTPS,所有情况下都使用ATS
)
如果你的应用只基于支持HTTPS的服务器,那么你太幸运了。你的应用不需要做任何改变。
2.Mix & Match(混合)
你的应用与一个不符合ATS要求的服务器工作是很有可能的.
我们拿三个域名来举例子:
1.api.insecuredomain.com
我们要让这个域名的请求使用http
Info.plist 配置中的XML源码如下所示:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>api.insecuredomain.com</key>
<dict>
<!--允许App进行不安全的HTTP请求-->
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<!--适用于这个特定域名下的所有子域-->
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
</dict>
在 plist 文件里显示如下:
我们定义的第一个“例外”(Exception)告诉ATS当与这个子域交互的时候撤销了必须使用HTTPS的要求。注意这个仅仅针对在“例外”(Exception)中声明了的子域。非常重要的一点是要理解NSExceptionAllowsInsecureHTTPLoads关键字并不仅仅只是与使用HTTPS相关。这个“例外”(Exception)指明了对于那个域名,所有的App Transport Security的要求都被撤销了。
2.cdn.domain.com
我们要让这个域名使用低于TLS1.2的加密协议
Info.plist 配置中的XML源码如下所示:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>cdn.somedomain.com</key>
<dict>
<key>NSThirdPartyExceptionMinimumTLSVersion</key>
<string>TLSv1.1</string>
</dict>
</dict>
</dict>
在 plist 文件里显示如下:
很可能你的应用是与一个支持HTTPS传输数据的服务器交互,但是并没有使用TLS 1.2或更高(上例中为TLS1.1)。在这种情况下,你定义一个“例外”(Exception),它指明应该使用的最小的TLS的版本。这比完全撤销那个域名的App Transport Security要更好更安全。
3.thatotherdomain.com
我们要让这个域名不支持ForwardSecrecy,同时,综合一下上面的用法
Info.plist 配置中的XML源码如下所示:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>thatotherdomain.com</key>
<dict>
<!--适用于这个特定域名下的所有子域-->
<key>NSIncludesSubdomains</key>
<true/>
<!--扩展可接受的密码列表:这个域名可以使用不支持 forward secrecy 协议的密码-->
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
<!--允许App进行不安全的HTTP请求-->
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<!--在这里声明所支持的 TLS 最低版本-->
<key>NSExceptionMinimumTLSVersion</key>
<string>TLSv1.1</string>
</dict>
</dict>
</dict>
在 plist 文件里显示如下:
NSIncludesSubdomains 关键字告诉 App Transport Security 这个“例外”(Exception)适用于这个特定域名的所有子域。这个“例外”(Exception)还进一步通过扩展可接受的密码列表来定义这个域名可以使用不支持forward secrecy( NSExceptionRequiresForwardSecrecy ) 协议的密码.
如果你的App中同时用到了这三个域名,那么应该是这样:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>api.insecuredomain.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<false/>
</dict>
<key>cdn.somedomain.com</key>
<dict>
<key>NSThirdPartyExceptionMinimumTLSVersion</key>
<string>TLSv1.1</string>
</dict>
<key>thatotherdomain.com</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
</dict>
</dict>
</dict>
在 plist 文件里显示如下:
3. Opt Out(禁用ATS)
上面是比较严谨的做法,指定了能访问哪些特定的HTTP。当然也有暴力的做法: 彻底倒退回不安全的HTTP网络请求,能任意进行HTTP请求,比如你在开发一款浏览器App,或者你想偷懒,或者后台想偷懒,或者公司不给你升级服务器...
你可以在Info.plist 配置中改用下面的XML源码:
<key>NSAppTransportSecurity</key>
<dict>
<!--彻底倒退回不安全的HTTP网络请求,能任意进行HTTP请求 (不建议这样做)-->
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
在 plist 文件里显示如下:
4. Opt Out With Exceptions(除特殊情况外,都不使用ATS)
上面已经介绍了三种情景,还有一种可能你也会遇到:
当你的应用撤消了App Transport Security,,但同时定义了一些“例外”(Exception)。当你的应用从很多的服务器上取数据,但是也要与一个你可控的API交互。在这种情况下,在应用的Info.plist文件中指定任何加载都是被允许的,但是你也指定了一个或多个“例外”(Exception)来表明哪些是必须要求 App Transport Security的。下面是Info.plist文件应该会有的内容:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSExceptionDomains</key>
<dict>
<key>api.tutsplus.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<false/>
</dict>
</dict>
</dict>
在 plist 文件里显示如下:
Certificate Transparency
虽然ATS大多数安全特性都是默认可用的,Certificate Transparency 是必须设置的。如果你有支持Certificate Transparency的证书,你可以检查NSRequiresCertificateTransparency关键字来使用Certificate Transparency。再次强调,如果你的证书不支持Certificate Transparency,此项需要设置为不可用。
如果需要调试一些由于采用了ATS而产生的问题,需要设置CFNETWORK_DIAGNOSTICS为1,这样就会打印出包含被访问的URL和ATS错误在内的NSURLSession错误信息。要确保处理了遇到的所有的错误消息,这样才能使ATS易于提高可靠性和扩展性。
说明
- 在使用Xcode7,并且Base SDK用的iOS9及以上版本,才需要网络适配ATS。
- 在OS X EI Capitan系统的终端中通过nscurl命令来诊断检查你的HTTPS服务配置是否满足Apple的ATS要求:
$ nscurl --verbose --ats-diagnostics https://<your_server_domain>
Demo
我们写一个Demo来说明会更清晰.
我们用https://www.apple.com/
来说明符合ATS的请求,这个不需要我们做任何额外工作,可以直接写请求,并得到数据.
我们用https://kyfw.12306.cn/
来说明不完全符合ATS的请求,12306的证书并没有经过知名CA认证(这就相当于我们后台自签名证书,没有经过知名CA认证).
首先,我们需要得到证书,12306的证书需要我们从其网站下载(火狐浏览器).
把pem转化为cer的命令为(我的文件取和存的地址都在桌面):
$ openssl x509 -inform PEM -in ~/Desktop/kyfw.12306.cn.pem -outform DER -out ~/Desktop/kyfw_12306_cn.cer
然后,把cer导入到工程中,记得勾选我们使用cer所对应的target,否则可能会出bug.
由于我们是本地导入的证书,所以我们要识别证书,这需要做一些验证,还好AFNetworking极大的简化了这个步骤.
manager.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
manager.securityPolicy.validatesDomainName = YES;
manager.securityPolicy.allowInvalidCertificates = YES;
如果是使用NSURLConnection,NSURLSession或者其他网络框架,就需要自己写验证了,可以参考这篇文章.
(本人对于NSURLConnection,NSURLSession验证证书的代码没搞懂,所以抱歉,这里就没法给出原生网络请求验证的Demo了- - !)
接着,按照上面的教程调整plist文件,对于12306的证书,我们不太清楚它具体那里没有符合ATS,我们需要尝试.
最终,这样可以调通:
源码:
#import "ViewController.h"
#import "AFNetworking.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self networking];
}
- (void)networking {
NSString * appleStr = @"https://www.apple.com/";
NSString * kyfwStr = @"https://kyfw.12306.cn/otn/";
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer = [AFHTTPRequestSerializer serializer];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
// 使用自签名证书要用以下代码来验证,其他情况要注掉
// manager.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
// manager.securityPolicy.validatesDomainName = YES;
// manager.securityPolicy.allowInvalidCertificates = YES;
[manager GET:appleStr parameters:nil success:^(AFHTTPRequestOperation * _Nonnull operation, id _Nonnull responseObject) {
NSLog(@"****JSON: %@", responseObject);
} failure:^(AFHTTPRequestOperation * _Nullable operation, NSError * _Nonnull error) {
NSLog(@"****Error: %@", error);
}];
}
@end
以上,我们详细列举了iOS实现https的情况,虽然内容很多,但其实,在实际开发中,需要我们做的工作并不多.总结来说,如果我们的请求符合ATS,则我们不需要做任何额外工作,如果不完全符合,比如,自签名证书,需要我们把自签名证书导入工程,调整plist文件,验证该证书即可.