一、簡單談?wù)凙TS(App Transport Security)
ATS(App Transport Security)是為了提高App與服務(wù)器之間安全傳輸數(shù)據(jù)一個(gè)特性岩睁,這個(gè)特性從iOS9和OSX10.11開始出現(xiàn)钞脂,它默認(rèn)需要滿足以下幾個(gè)條件:
服務(wù)器TLS版本至少是1.2版本
連接加密只允許幾種先進(jìn)的加密
證書必須使用SHA256或者更好的哈希算法進(jìn)行簽名,要么是2048位或者更長的RSA密鑰捕儒,要么就是256位或更長的ECC密鑰冰啃。
如果想了解哪幾種先進(jìn)的加密是被允許的,詳情請(qǐng)見官方文檔App Transport Security Technote
二刘莹、搭建HTTPS服務(wù)器
搭
建HTTPS服務(wù)器有兩種方式阎毅,一種是創(chuàng)建證書請(qǐng)求,然后到權(quán)威機(jī)構(gòu)認(rèn)證点弯,隨之配置到服務(wù)器扇调;另外一種是自建證書,然后配置給服務(wù)器抢肛。第一種方式搭建的
HTTPS服務(wù)器當(dāng)然是最優(yōu)的了狼钮,建立網(wǎng)站的話,直接就會(huì)被信任捡絮,而作為移動(dòng)端app的服務(wù)器時(shí)熬芜,也不需要為ATS做過多的適配。雖然說權(quán)威的機(jī)構(gòu)認(rèn)證都
是需要錢的福稳,但是如今也不乏存在免費(fèi)的第三方認(rèn)證機(jī)構(gòu)涎拉;第二種方式搭建的HTTPS服務(wù)器,對(duì)于網(wǎng)站來說完全不可行的圆,用戶打開時(shí)直接彈出一個(gè)警告提醒鼓拧,說
這是一個(gè)不受信任的網(wǎng)站,讓用戶是否繼續(xù)略板,體驗(yàn)很差毁枯,而且讓用戶感覺網(wǎng)站不安全慈缔。對(duì)于移動(dòng)端來說叮称,在iOS9出現(xiàn)之前,這個(gè)沒什么問題藐鹤,但是在iOS9出
來之后瓤檐,第二種方式是通不過ATS特性,需要將NSAllowsArbitraryLoads設(shè)置為YES才行娱节。所以挠蛉,我推薦使用第一種方式搭建
HTTPS服務(wù)器媒佣。
下面捐韩,咱們來說說這兩種方式都如何進(jìn)行操作。
第一種、使用CA機(jī)構(gòu)認(rèn)證的證書搭建HTTPS服務(wù)器
1掐隐、創(chuàng)建證書請(qǐng)求,并提交給CA機(jī)構(gòu)認(rèn)證
1
2
3
4
5
6#生成私鑰
openssl?genrsa?-des3?-out?private.key?2048
#生成服務(wù)器的私鑰诀紊,去除密鑰口令
openssl?rsa?-inprivate.key?-out?server.key
#生成證書請(qǐng)求
openssl?req?-new-key?private.key?-out?server.csr
將生成server.csr提交給CA機(jī)構(gòu)垦垂,CA機(jī)構(gòu)對(duì)它進(jìn)行簽名之后,然后會(huì)生成簽名后的根證書和服務(wù)器證書發(fā)送給你带饱,這個(gè)時(shí)候的證書就是CA認(rèn)證之后的證書毡代。我們這里將根證書和服務(wù)器證書分別改名為ca.crt和serve.crt。
2勺疼、配置Apache服務(wù)器
將ca.crt教寂、server.key、server.crt上傳到阿里云服務(wù)器执庐,使用SSH登陸進(jìn)入這三個(gè)文件的目錄酪耕,執(zhí)行下面命令
1
2
3
4
5mkdir?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進(jìn)行修改:
1
2
3
4
5
6
7
8#?指定服務(wù)器證書位置
SSLCertificateFile"/alidata/server/httpd/conf/ssl/server.crt"
#?指定服務(wù)器證書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
1
2
3
4
5
6SSLCertificateFile????/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服務(wù)器婚被,在瀏覽器輸入網(wǎng)址查看是否配置成功。我這里是個(gè)人使用梳虽,申請(qǐng)的是免費(fèi)的證書址芯,我申請(qǐng)證書的網(wǎng)站是沃通。
三窜觉、使用nscurl對(duì)服務(wù)器進(jìn)行檢測(cè)
搭建完HTTPS服務(wù)器之后谷炸,可以使用nscurl命令來進(jìn)行檢測(cè),查看建立的HTTPS服務(wù)器是否能通過ATS特性禀挫。
1
nscurl?--ats-diagnostics?--verbose?https://casetree.cn
如
果HTTPS服務(wù)器能通過ATS特性旬陡,則上面所有測(cè)試案例都是PASS;如果某一項(xiàng)的Reuslt是FAIL语婴,就找到ATS
Dictionary來查看描孟,就能知道HTTPS服務(wù)器不滿足ATS哪個(gè)條件。
這里我前面碰到一個(gè)問題砰左,就是自建證書的時(shí)候匿醒,通過此命令進(jìn)行測(cè)試時(shí),發(fā)現(xiàn)Result全是FAIL缠导,而且在iOS的代碼測(cè)試中也出現(xiàn)了一個(gè)很奇怪的現(xiàn)
象廉羔,就是相同的代碼,在iOS8.4請(qǐng)求數(shù)據(jù)完全正常僻造,但是在iOS9上憋他,直接是連接失敗孩饼。最終發(fā)現(xiàn),其實(shí)就是因?yàn)樽越ㄗC書不受信任竹挡,是通不過ATS的捣辆,
除非將NSAllowsArbitraryLoads設(shè)置為YES。
四此迅、iOS客戶端
在
上面的第二大步驟當(dāng)中汽畴,HTTPS服務(wù)器滿足ATS默認(rèn)的條件,而且SSL證書是通過權(quán)威的CA機(jī)構(gòu)認(rèn)證過的耸序,那么我們?cè)谑褂肵code7開發(fā)的時(shí)候忍些,對(duì)
網(wǎng)絡(luò)的適配什么都不用做,我們也能正常與服務(wù)器通信坎怪。但是罢坝,當(dāng)我們對(duì)安全性有更高的要求時(shí)或者我們自建證書時(shí),我們需要本地導(dǎo)入證書來進(jìn)行驗(yàn)證搅窿。
那么嘁酿,如何本地導(dǎo)入證書進(jìn)行驗(yàn)證呢?
在這里先提一下男应,由于iOS客戶端支持的證書是DER格式的闹司,我們需要?jiǎng)?chuàng)建客戶端證書。創(chuàng)建客戶端證書沐飘,直接將服務(wù)端的CA根證書導(dǎo)出成DER格式就行游桩。
1
openssl??x509??-inform?PEM??-outform?DER?-inca.crt?-out?ca.cer
導(dǎo)入完證書之后,我們分別來說說使用NSURLSession和AFNetworking來進(jìn)行本地驗(yàn)證耐朴。
首先借卧,來說說使用NSURLSession驗(yàn)證
驗(yàn)證步驟如下:
導(dǎo)入CA根證書到工程中,即我們創(chuàng)建的ca.cer
獲
取trust
object筛峭,通過SecCertificateCreateWithData方法讀取導(dǎo)入的證書的數(shù)據(jù)生成一個(gè)證書對(duì)象铐刘,然后通過
SecTrustSetAnchorCertificates 設(shè)置這個(gè)證書為trust object的信任根證書(trusted anchor)
通過SecTrustEvaluate方法去驗(yàn)證trust object
下面是主要OC實(shí)現(xiàn)代碼,Demo工程我也放在github上了影晓,有OC和Swift兩種語言镰吵,下載Demo請(qǐng)點(diǎn)擊HTTPSConnectDemo。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48-?(void)viewDidLoad?{
[superviewDidLoad];
//導(dǎo)入客戶端證書
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];
//發(fā)送請(qǐng)求
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?*?__nullable?credential))completionHandler{
OSStatus?err;
NSURLSessionAuthChallengeDisposition?disposition?=?NSURLSessionAuthChallengePerformDefaultHandling;
SecTrustResultType??trustResult?=?kSecTrustResultInvalid;
NSURLCredential?*credential?=?nil;
//獲取服務(wù)器的trust?object
SecTrustRef?serverTrust?=?challenge.protectionSpace.serverTrust;
//將讀取的證書設(shè)置為serverTrust的根證書
err?=?SecTrustSetAnchorCertificates(serverTrust,?(__bridge?CFArrayRef)self.trustedCerArr);
if(err?==?noErr){
//通過本地導(dǎo)入的證書來驗(yàn)證服務(wù)器的證書是否可信俯艰,如果將SecTrustSetAnchorCertificatesOnly設(shè)置為NO捡遍,則只要通過本地或者系統(tǒng)證書鏈任何一方認(rèn)證就行
err?=?SecTrustEvaluate(serverTrust,?&trustResult);
}
if(err?==?errSecSuccess?&&?(trustResult?==?kSecTrustResultProceed?||?trustResult?==?kSecTrustResultUnspecified)){
//認(rèn)證成功锌订,則創(chuàng)建一個(gè)憑證返回給服務(wù)器
disposition?=?NSURLSessionAuthChallengeUseCredential;
credential?=?[NSURLCredential?credentialForTrust:serverTrust];
}
else{
disposition?=?NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
//回調(diào)憑證竹握,傳遞給服務(wù)器
if(completionHandler){
completionHandler(disposition,?credential);
}
}
注意:
1、SecTrustSetAnchorCertificates方法會(huì)設(shè)置一個(gè)標(biāo)示去屏蔽trust object對(duì)其它根證書的信任辆飘;如果你也想信任系統(tǒng)默認(rèn)的根證書啦辐,請(qǐng)調(diào)用SecTrustSetAnchorCertificatesOnly方法谓传,清空這個(gè)標(biāo)示(設(shè)置為NO) 2、驗(yàn)證的方法不僅僅只有這一種芹关,更多的驗(yàn)證方法续挟,請(qǐng)參考HTTPS Server Trust Evaluation
下面,來談?wù)凙FNetworking是如何驗(yàn)證的侥衬,我們?nèi)绾问褂肁FNetworking诗祸。
AFNetworking的證書驗(yàn)證工作是由AFSecurityPolicy來完成的,所以這里我們主要來了解一下AFSecurityPolicy轴总。注意:我這里使用的是AFNetworking2.6.0直颅,它跟2.5.0是有區(qū)別的。
說到AFSecurityPolicy怀樟,我們必須要提到它三個(gè)重要的屬性功偿,如下:
1
2
3@property?(readonly,?nonatomic,?assign)?AFSSLPinningMode?SSLPinningMode;
@property?(nonatomic,?assign)?BOOL?allowInvalidCertificates;
@property?(nonatomic,?assign)?BOOL?validatesDomainName;
SSLPingMode
是最重要的屬性,它標(biāo)明了AFSecurityPolicy是以何種方式來驗(yàn)證往堡。它是一個(gè)枚舉類型械荷,這個(gè)枚舉類型有三個(gè)值,分別是
AFSSLPinningModeNone虑灰、AFSSLPinningModePublicKey吨瞎、
AFSSLPinningModeCertificate。其中穆咐,AFSSLPinningModeNone代表了AFSecurityPolicy不做
更嚴(yán)格的驗(yàn)證关拒,只要是系統(tǒng)信任的證書就可以通過驗(yàn)證,不過庸娱,它受到allowInvalidCertificates和
validatesDomainName的影響着绊;AFSSLPinningModePublicKey是通過比較證書當(dāng)中公鑰(PublicKey)部分
來進(jìn)行驗(yàn)證,通過SecTrustCopyPublicKey方法獲取本地證書和服務(wù)器證書熟尉,然后進(jìn)行比較归露,如果有一個(gè)相同,則通過驗(yàn)證斤儿,此方式主要適用
于自建證書搭建的HTTPS服務(wù)器和需要較高安全要求的驗(yàn)證剧包;AFSSLPinningModeCertificate則是直接將本地的證書設(shè)置為信任的
根證書,然后來進(jìn)行判斷往果,并且比較本地證書的內(nèi)容和服務(wù)器證書內(nèi)容是否相同疆液,來進(jìn)行二次判斷,此方式適用于較高安全要求的驗(yàn)證陕贮。
allowInvalidCertificates屬性代表是否允許不信任的證書通過驗(yàn)證堕油,默認(rèn)為NO。
validatesDomainName屬性代表是否驗(yàn)證主機(jī)名,默認(rèn)為YES掉缺。
接下來卜录,我們說下驗(yàn)證流程。驗(yàn)證流程主要放在AFSecurityPolicy的- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain方法當(dāng)中眶明。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77-?(BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString?*)domain
{
//當(dāng)使用自建證書驗(yàn)證域名時(shí)艰毒,需要使用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.");
returnNO;
}
NSMutableArray?*policies?=?[NSMutableArray?array];
//需要驗(yàn)證域名時(shí),需要添加一個(gè)驗(yàn)證域名的策略
if(self.validatesDomainName)?{
[policies?addObject:(__bridge_transfer?id)SecPolicyCreateSSL(true,?(__bridge?CFStringRef)domain)];
}else{
[policies?addObject:(__bridge_transfer?id)SecPolicyCreateBasicX509()];
}
//設(shè)置驗(yàn)證的策略搜囱,可以是多個(gè)
SecTrustSetPolicies(serverTrust,?(__bridge?CFArrayRef)policies);
//SSLPinningMode為AFSSLPinningModeNone時(shí)丑瞧,allowInvalidCertificates為YES,則代表服務(wù)器任何證書都能驗(yàn)證通過蜀肘;如果它為NO嗦篱,則需要判斷此服務(wù)器證書是否是系統(tǒng)信任的證書
if(self.SSLPinningMode?==?AFSSLPinningModeNone)?{
if(self.allowInvalidCertificates?||?AFServerTrustIsValid(serverTrust)){
returnYES;
}else{
returnNO;
}
}elseif(!AFServerTrustIsValid(serverTrust)?&&?!self.allowInvalidCertificates)?{
returnNO;
}
//獲取服務(wù)器證書的內(nèi)容
NSArray?*serverCertificates?=?AFCertificateTrustChainForServerTrust(serverTrust);
switch(self.SSLPinningMode)?{
caseAFSSLPinningModeNone:
default:
returnNO;
caseAFSSLPinningModeCertificate:?{
//AFSSLPinningModeCertificate是直接將本地的證書設(shè)置為信任的根證書,然后來進(jìn)行判斷幌缝,并且比較本地證書的內(nèi)容和服務(wù)器證書內(nèi)容是否相同灸促,如果有一個(gè)相同則返回YES
NSMutableArray?*pinnedCertificates?=?[NSMutableArray?array];
for(NSData?*certificateDatainself.pinnedCertificates)?{
[pinnedCertificates?addObject:(__bridge_transfer?id)SecCertificateCreateWithData(NULL,?(__bridge?CFDataRef)certificateData)];
}
//設(shè)置本地的證書為根證書
SecTrustSetAnchorCertificates(serverTrust,?(__bridge?CFArrayRef)pinnedCertificates);
//通過本地的證書來判斷服務(wù)器證書是否可信,不可信涵卵,則驗(yàn)證不通過
if(!AFServerTrustIsValid(serverTrust))?{
returnNO;
}
//判斷本地證書和服務(wù)器證書的內(nèi)容是否相同
NSUInteger?trustedCertificateCount?=?0;
for(NSData?*trustChainCertificateinserverCertificates)?{
if([self.pinnedCertificates?containsObject:trustChainCertificate])?{
trustedCertificateCount++;
}
}
returntrustedCertificateCount?>?0;
}
caseAFSSLPinningModePublicKey:?{
//AFSSLPinningModePublicKey是通過比較證書當(dāng)中公鑰(PublicKey)部分來進(jìn)行驗(yàn)證浴栽,通過SecTrustCopyPublicKey方法獲取本地證書和服務(wù)器證書,然后進(jìn)行比較轿偎,如果有一個(gè)相同典鸡,則通過驗(yàn)證
NSUInteger?trustedPublicKeyCount?=?0;
NSArray?*publicKeys?=?AFPublicKeyTrustChainForServerTrust(serverTrust);
//判斷服務(wù)器證書的公鑰與本地的證書公鑰是否相同,相同則客戶端認(rèn)證通過
for(id?trustChainPublicKeyinpublicKeys)?{
for(id?pinnedPublicKeyinself.pinnedPublicKeys)?{
if(AFSecKeyIsEqualToKey((__bridge?SecKeyRef)trustChainPublicKey,?(__bridge?SecKeyRef)pinnedPublicKey))?{
trustedPublicKeyCount?+=?1;
}
}
}
returntrustedPublicKeyCount?>?0;
}
}
returnNO;
}
說了驗(yàn)證流程坏晦,我們最后來看看AFNetworking怎么使用萝玷,代碼如下:
1
2
3
4
5
6
7_httpClient?=?[[BGAFHTTPClient?alloc]?initWithBaseURL:[NSURL?URLWithString:baseURL]];
AFSecurityPolicy?*policy?=?[AFSecurityPolicy?policyWithPinningMode:AFSSLPinningModeCertificate];
//是否允許CA不信任的證書通過
policy.allowInvalidCertificates?=?YES;
//是否驗(yàn)證主機(jī)名
policy.validatesDomainName?=?YES;
_httpClient.securityPolicy?=?policy;
這里我就沒有建立Demo了,如果要看的話昆婿,可以看看我寫的一個(gè)框架BGNetwork球碉,里面的Demo對(duì)ATS進(jìn)行了適配,AFNetworking的使用放在BGNetworkConnector類里面的- (instancetype)initWithBaseURL:(NSString *)baseURL delegate:(id)delegate初始化方法中仓蛆。
五睁冬、適配ATS
前面的內(nèi)容講述都是滿足ATS特性的情況,但若是服務(wù)器是自建證書搭建的看疙,或者TLS版本是1.0的話豆拨,服務(wù)器又不能輕易改動(dòng),那么我們客戶端如何適配呢能庆? 不急施禾,我們可以在工程中的Info.plist文件當(dāng)中進(jìn)行設(shè)置,主要參照下圖:
如果是自建證書搁胆,沒有經(jīng)過權(quán)威機(jī)構(gòu)認(rèn)證的證書弥搞,那么需要將NSAllowsArbitraryLoads設(shè)置為YES才能通過邮绿。NSAllowsArbitraryLoads為YES,以前的HTTP請(qǐng)求也能通過拓巧。
如果是認(rèn)證過的證書斯碌,那么可以通過nscurl --ats-diagnostics --verbosehttps://casetree.cn這樣的命令來查看服務(wù)器支持的ATS Dictionary一死,然后進(jìn)行對(duì)應(yīng)的設(shè)置肛度。