iOS 9系统已经出来了,而网络方面的ATS(App Transport Security)特性能够说每一个人都要经历。而我这篇博客,就是结合我最近几天的经历,来谈谈从服务器到iOS客户端对ATS的适配。php
1、简单谈谈ATS(App Transport Security)html
ATS(App Transport Security)是为了提升App与服务器之间安全传输数据一个特性,这个特性从iOS9和OSX10.11开始出现,它默认须要知足如下几个条件:ios
服务器TLS版本至少是1.2版本git
链接加密只容许几种先进的加密github
证书必须使用SHA256或者更好的哈希算法进行签名,要么是2048位或者更长的RSA密钥,要么就是256位或更长的ECC密钥。web
若是想了解哪几种先进的加密是被容许的,详情请见官方文档App Transport Security Technote算法
2、搭建HTTPS服务器vim
搭 建HTTPS服务器有两种方式,一种是建立证书请求,而后到权威机构认证,随之配置到服务器;另一种是自建证书,而后配置给服务器。第一种方式搭建的 HTTPS服务器固然是最优的了,创建网站的话,直接就会被信任,而做为移动端app的服务器时,也不须要为ATS作过多的适配。虽说权威的机构认证都 是须要钱的,可是现在也不乏存在免费的第三方认证机构;第二种方式搭建的HTTPS服务器,对于网站来讲彻底不可行,用户打开时直接弹出一个警告提醒,说 这是一个不受信任的网站,让用户是否继续,体验不好,并且让用户感受网站不安全。对于移动端来讲,在iOS9出现以前,这个没什么问题,可是在iOS9出 来以后,第二种方式是通不过ATS特性,须要将NSAllowsArbitraryLoads设置为YES才行。因此,我推荐使用第一种方式搭建 HTTPS服务器。浏览器
下面,我们来讲说这两种方式都如何进行操做。安全
第一种、使用CA机构认证的证书搭建HTTPS服务器
一、建立证书请求,并提交给CA机构认证
#生成私钥
openssl genrsa -des3 -out private.key 2048
#生成服务器的私钥,去除密钥口令
openssl rsa -
in
private.key -out server.key
#生成证书请求
openssl req -
new
-key private.key -out server.csr
将生成server.csr提交给CA机构,CA机构对它进行签名以后,而后会生成签名后的根证书和服务器证书发送给你,这个时候的证书就是CA认证以后的证书。咱们这里将根证书和服务器证书分别更名为ca.crt和serve.crt。
二、配置Apache服务器
将ca.crt、server.key、server.crt上传到阿里云服务器,使用SSH登录进入这三个文件的目录,执行下面命令
mkdir ssl
cp server.crt /alidata/server/httpd/conf/ssl/server.crt
cp server.key /alidata/server/httpd/conf/ssl/server.key
cp demoCA/cacert.pem /alidata/server/httpd/conf/ssl/ca.crt
cp -r ssl /alidata/server/httpd/conf/
编 辑/alidata/server/httpd/conf/extra/httpd-ssl.conf文件,找到SSLCertificateFile、 SSLCertificateKeyFile、SSLCACertificatePath、SSLCACertificateFile进行修改:
# 指定服务器证书位置
SSLCertificateFile
"/alidata/server/httpd/conf/ssl/server.crt"
# 指定服务器证书key位置
SSLCertificateKeyFile
"/alidata/server/httpd/conf/ssl/server.key"
# 证书目录
SSLCACertificatePath
"/alidata/server/httpd/conf/ssl"
# 根证书位置
SSLCACertificateFile
"/alidata/server/httpd/conf/ssl/ca.crt"
修改vhost配置vim /alidata/server/httpd/conf/vhosts/phpwind.conf
SSLCertificateFile /alidata/server/httpd/conf/ssl/server.crt
SSLCertificateKeyFile /alidata/server/httpd/conf/ssl/server.key
SSLCACertificatePath /alidata/server/httpd/conf/ssl
SSLCACertificateFile /alidata/server/httpd/conf/ssl/ca.crt
ServerName www.casetree.cn
DocumentRoot /alidata/www
最后,重启Apache服务器,在浏览器输入网址查看是否配置成功。我这里是我的使用,申请的是免费的证书,我申请证书的网站是沃通。
搭建的成果:https://www.casetree.cn
第二种、自建证书配置HTTPS服务器
请查看个人上一篇自建证书配置HTTPS服务器
3、使用nscurl对服务器进行检测
搭建完HTTPS服务器以后,可使用nscurl命令来进行检测,查看创建的HTTPS服务器是否能经过ATS特性。
nscurl --ats-diagnostics --verbose https:
//casetree.cn
如 果HTTPS服务器能经过ATS特性,则上面全部测试案例都是PASS;若是某一项的Reuslt是FAIL,就找到ATS Dictionary来查看,就能知道HTTPS服务器不知足ATS哪一个条件。 这里我前面碰到一个问题,就是自建证书的时候,经过此命令进行测试时,发现Result全是FAIL,并且在iOS的代码测试中也出现了一个很奇怪的现 象,就是相同的代码,在iOS8.4请求数据彻底正常,可是在iOS9上,直接是链接失败。最终发现,其实就是由于自建证书不受信任,是通不过ATS的, 除非将NSAllowsArbitraryLoads设置为YES。
4、iOS客户端
在 上面的第二大步骤当中,HTTPS服务器知足ATS默认的条件,并且SSL证书是经过权威的CA机构认证过的,那么咱们在使用Xcode7开发的时候,对 网络的适配什么都不用作,咱们也能正常与服务器通讯。可是,当咱们对安全性有更高的要求时或者咱们自建证书时,咱们须要本地导入证书来进行验证。
那么,如何本地导入证书进行验证呢?
在这里先提一下,因为iOS客户端支持的证书是DER格式的,咱们须要建立客户端证书。建立客户端证书,直接将服务端的CA根证书导出成DER格式就行。
openssl x509 -inform PEM -outform DER -
in
ca.crt -out ca.cer
导入完证书以后,咱们分别来讲说使用NSURLSession和AFNetworking来进行本地验证。
首先,来讲说使用NSURLSession验证
验证步骤以下:
导入CA根证书到工程中,即咱们建立的ca.cer
获取trust object,经过SecCertificateCreateWithData方法读取导入的证书的数据生成一个证书对象,而后经过SecTrustSetAnchorCertificates 设置这个证书为trust object的信任根证书(trusted anchor)
经过SecTrustEvaluate方法去验证trust object
下面是主要OC实现代码,Demo工程我也放在github上了,有OC和Swift两种语言,下载Demo请点击HTTPSConnectDemo。
- (void)viewDidLoad {
[
super
viewDidLoad];
//导入客户端证书
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@
"ca"
ofType:@
"cer"
];
NSData *data = [NSData dataWithContentsOfFile:cerPath];
SecCertificateRef certificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef) data);
self.trustedCerArr = @[(__bridge_transfer id)certificate];
//发送请求
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionDataTask *task = [session dataTaskWithRequest:[NSURLRequest requestWithURL:testURL]];
[task resume];
// Do any additional setup after loading the view, typically from a nib.
}
#pragma mark - NSURLSessionDelegate
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler{
OSStatus err;
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
SecTrustResultType trustResult = kSecTrustResultInvalid;
NSURLCredential *credential = nil;
//获取服务器的trust object
SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
//将读取的证书设置为serverTrust的根证书
err = SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)self.trustedCerArr);
if
(err == noErr){
//经过本地导入的证书来验证服务器的证书是否可信,若是将SecTrustSetAnchorCertificatesOnly设置为NO,则只要经过本地或者系统证书链任何一方认证就行
err = SecTrustEvaluate(serverTrust, &trustResult);
}
if
(err == errSecSuccess && (trustResult == kSecTrustResultProceed || trustResult == kSecTrustResultUnspecified)){
//认证成功,则建立一个凭证返回给服务器
disposition = NSURLSessionAuthChallengeUseCredential;
credential = [NSURLCredential credentialForTrust:serverTrust];
}
else
{
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
//回调凭证,传递给服务器
if
(completionHandler){
completionHandler(disposition, credential);
}
}
注意:
一、 SecTrustSetAnchorCertificates方法会设置一个标示去屏蔽trust object对其它根证书的信任;若是你也想信任系统默认的根证书,请调用SecTrustSetAnchorCertificatesOnly方法,清 空这个标示(设置为NO) 二、验证的方法不只仅只有这一种,更多的验证方法,请参考HTTPS Server Trust Evaluation
下面,来谈谈AFNetworking是如何验证的,咱们如何使用AFNetworking。
AFNetworking的证书验证工做是由AFSecurityPolicy来完成的,因此这里咱们主要来了解一下AFSecurityPolicy。注意:我这里使用的是AFNetworking2.6.0,它跟2.5.0是有区别的。
说到AFSecurityPolicy,咱们必需要提到它三个重要的属性,以下:
@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode;
@property (nonatomic, assign) BOOL allowInvalidCertificates;
@property (nonatomic, assign) BOOL validatesDomainName;
SSLPingMode 是最重要的属性,它标明了AFSecurityPolicy是以何种方式来验证。它是一个枚举类型,这个枚举类型有三个值,分别是 AFSSLPinningModeNone、AFSSLPinningModePublicKey、 AFSSLPinningModeCertificate。其中,AFSSLPinningModeNone表明了AFSecurityPolicy不作 更严格的验证,只要是系统信任的证书就能够经过验证,不过,它受到allowInvalidCertificates和 validatesDomainName的影响;AFSSLPinningModePublicKey是经过比较证书当中公钥(PublicKey)部分 来进行验证,经过SecTrustCopyPublicKey方法获取本地证书和服务器证书,而后进行比较,若是有一个相同,则经过验证,此方式主要适用 于自建证书搭建的HTTPS服务器和须要较高安全要求的验证;AFSSLPinningModeCertificate则是直接将本地的证书设置为信任的 根证书,而后来进行判断,而且比较本地证书的内容和服务器证书内容是否相同,来进行二次判断,此方式适用于较高安全要求的验证。
allowInvalidCertificates属性表明是否容许不信任的证书经过验证,默认为NO。
validatesDomainName属性表明是否验证主机名,默认为YES。
接下来,咱们说下验证流程。验证流程主要放在AFSecurityPolicy的- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain方法当中。
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain
{
//当使用自建证书验证域名时,须要使用AFSSLPinningModePublicKey或者AFSSLPinningModeCertificate
if
(domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
NSLog(@
"In order to validate a domain name for self signed certificates, you MUST use pinning."
);
return
NO;
}
NSMutableArray *policies = [NSMutableArray array];
//须要验证域名时,须要添加一个验证域名的策略
if
(self.validatesDomainName) {
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(
true
, (__bridge CFStringRef)domain)];
}
else
{
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}
//设置验证的策略,能够是多个
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
//SSLPinningMode为AFSSLPinningModeNone时,allowInvalidCertificates为YES,则表明服务器任何证书都能验证经过;若是它为NO,则须要判断此服务器证书是不是系统信任的证书
if
(self.SSLPinningMode == AFSSLPinningModeNone) {
if
(self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust)){
return
YES;
}
else
{
return
NO;
}
}
else
if
(!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
return
NO;
}
//获取服务器证书的内容
NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
switch
(self.SSLPinningMode) {
case
AFSSLPinningModeNone:
default
:
return
NO;
case
AFSSLPinningModeCertificate: {
//AFSSLPinningModeCertificate是直接将本地的证书设置为信任的根证书,而后来进行判断,而且比较本地证书的内容和服务器证书内容是否相同,若是有一个相同则返回YES
NSMutableArray *pinnedCertificates = [NSMutableArray array];
for
(NSData *certificateData
in
self.pinnedCertificates) {
[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
}
//设置本地的证书为根证书
SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);
//经过本地的证书来判断服务器证书是否可信,不可信,则验证不经过
if
(!AFServerTrustIsValid(serverTrust)) {
return
NO;
}
//判断本地证书和服务器证书的内容是否相同
NSUInteger trustedCertificateCount = 0;
for
(NSData *trustChainCertificate
in
serverCertificates) {
if
([self.pinnedCertificates containsObject:trustChainCertificate]) {
trustedCertificateCount++;
}
}
return
trustedCertificateCount > 0;
}
case
AFSSLPinningModePublicKey: {
//AFSSLPinningModePublicKey是经过比较证书当中公钥(PublicKey)部分来进行验证,经过SecTrustCopyPublicKey方法获取本地证书和服务器证书,而后进行比较,若是有一个相同,则经过验证
NSUInteger trustedPublicKeyCount = 0;
NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);
//判断服务器证书的公钥与本地的证书公钥是否相同,相同则客户端认证经过
for
(id trustChainPublicKey
in
publicKeys) {
for
(id pinnedPublicKey
in
self.pinnedPublicKeys) {
if
(AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
trustedPublicKeyCount += 1;
}
}
}
return
trustedPublicKeyCount > 0;
}
}
return
NO;
}
说了验证流程,咱们最后来看看AFNetworking怎么使用,代码以下:
_httpClient = [[BGAFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:baseURL]];
AFSecurityPolicy *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
//是否容许CA不信任的证书经过
policy.allowInvalidCertificates = YES;
//是否验证主机名
policy.validatesDomainName = YES;
_httpClient.securityPolicy = policy;
这里我就没有创建Demo了,若是要看的话,能够看看我写的一个框架BGNetwork,里面的Demo对ATS进行了适配,AFNetworking的使用放在BGNetworkConnector类里面的- (instancetype)initWithBaseURL:(NSString *)baseURL delegate:(id)delegate初始化方法中。
5、适配ATS
前面的内容讲述都是知足ATS特性的状况,但如果服务器是自建证书搭建的,或者TLS版本是1.0的话,服务器又不能轻易改动,那么咱们客户端如何适配呢? 不急,咱们能够在工程中的Info.plist文件当中进行设置,主要参照下图:
若是是自建证书,没有通过权威机构认证的证书,那么须要将NSAllowsArbitraryLoads设置为YES才能经过。NSAllowsArbitraryLoads为YES,之前的HTTP请求也能经过。
若是是认证过的证书,那么能够经过nscurl --ats-diagnostics --verbose https://casetree.cn这样的命令来查看服务器支持的ATS Dictionary,而后进行对应的设置。
适配的部分,也能够参照Demo1_iOS9网络适配_ATS:改用更安全的HTTPS
总结
回顾前面的内容,总结一下,主要讲了一下几点内容:
ATS须要知足的条件
如何创建证书,搭建HTTPS服务器
使用nscurl命令来检测HTTPS服务器是否知足ATS特性
客户端的适配,讲述了NSURLSession和AFNetworking的使用
讲述了若是创建的服务器不知足ATS的条件时,咱们如何适配
参考