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的條件時,咱們如何適配
參考