iOS 9系统已经出来了,而网络方面的ATS(App Transport Security)特性可以说每个人都要经历。而我这篇博客,便是结合我最近几天的经历,来谈谈从做事器到iOS客户端对ATS的适配。
一、大略谈谈ATS(App Transport Security)
ATS(App Transport Security)是为了提高App与做事器之间安全传输数据一个特性,这个特性从iOS9和OSX10.11开始涌现,它默认须要知足以下几个条件:

做事器TLS版本至少是1.2版本
连接加密只许可几种前辈的加密
证书必须利用SHA256或者更好的哈希算法进行署名,要么是2048位或者更长的RSA密钥,要么便是256位或更长的ECC密钥。
如果想理解哪几种前辈的加密是被许可的,详情请见官方文档App Transport Security Technote
二、搭建HTTPS做事器
搭建HTTPS做事器有两种办法,一种是创建证书要求,然后到威信机构认证,随之配置到做事器;其余一种是自建证书,然后配置给做事器。第一种办法搭建的HTTPS做事器当然是最优的了,建立网站的话,直接就会被信赖,而作为移动端app的做事器时,也不须要为ATS做过多的适配。虽然说威信的机构认证都是须要钱的,但是如今也不乏存在免费的第三方认证机构;第二种办法搭建的HTTPS做事器,对付网站来说完备不可行,用户打开时直接弹出一个警告提醒,说这是一个不受信赖的网站,让用户是否连续,体验很差,而且让用户觉得网站不屈安。对付移动端来说,在iOS9涌现之前,这个没什么问题,但是在iOS9出来之后,第二种办法是通不过ATS特性,须要将NSAllowsArbitraryLoads设置为YES才行。以是,我推举利用第一种办法搭建HTTPS做事器。
下面,咱们来说说这两种办法都如何进行操作。
第一种、利用CA机构认证的证书搭建HTTPS做事器
1、创建证书要求,并提交给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。
2、配置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做事器
三、利用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。
四、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(, (__bridge CFDataRef) data);
self.trustedCerArr = @[(__bridge_transfer id)certificate];
//发送要求
NSURL testURL = [NSURL URLWithString:@
\"大众https://casetree.cn/web/test/demo.php\公众
];
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 __able 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);
}
}
把稳:
1、SecTrustSetAnchorCertificates方法会设置一个标示去屏蔽trust object对其它根证书的信赖;如果你也想信赖系统默认的根证书,请调用SecTrustSetAnchorCertificatesOnly方法,清空这个标示(设置为NO) 2、验证的方法不仅仅只有这一种,更多的验证方法,请参考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(, (__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初始化方法中。
五、适配ATS
前面的内容讲述都是知足ATS特性的情形,但若是做事器是自建证书搭建的,或者TLS版本是1.0的话,做事器又不能轻易改动,那么我们客户端如何适配呢? 不急,我们可以在工程中的Info.plist文件当中进行设置,紧张参照下图:
如果是自建证书,没有经由威信机构认证的证书,那么须要将NSAllowsArbitraryLoads设置为YES才能通过。NSAllowsArbitraryLoads为YES,以前的HTTP要求也能通过。
如果是认证过的证书,那么可以通过nscurl --ats-diagnostics --verbose https://casetree.cn这样的命令来查看做事器支持的ATS Dictionary,然后进行对应的设置。
总结
回顾前面的内容,总结一下,紧张讲了一下几点内容:
ATS须要知足的条件
如何建立证书,搭建HTTPS做事器
利用nscurl命令来检测HTTPS做事器是否知足ATS特性
客户真个适配,讲述了NSURLSession和AFNetworking的利用
讲述了如果建立的做事器不知足ATS的条件时,我们如何适配
参考
点击下图,参加CocoaChina最新活动
微旗子暗记:CocoaChinabbs
(长按上图,可自动识别二维码)
--------------------------------------
商务互助QQ:645047738
投稿邮箱:support@cocoachina.com