更新:蘋(píng)果官方發(fā)布公告,暫緩HTTPS的強(qiáng)制性要求,具體要求時(shí)間再另行通知,要配置的開(kāi)發(fā)者們可以暫緩一口氣了。
在16年的WWDC中,Apple已表示將從2017年1月1日起,所有新提交的App必須強(qiáng)制性應(yīng)用HTTPS協(xié)議來(lái)進(jìn)行網(wǎng)絡(luò)請(qǐng)求。默認(rèn)情況下非HTTPS的網(wǎng)絡(luò)訪問(wèn)是禁止的并且不能再通過(guò)簡(jiǎn)單粗暴的向Info.plist中添加NSAllowsArbitraryLoads
設(shè)置繞過(guò)ATS(App Transport Security)的限制(否則須在應(yīng)用審核時(shí)進(jìn)行說(shuō)明并很可能會(huì)被拒)。所以還未進(jìn)行相應(yīng)配置的公司需要盡快將升級(jí)為HTTPS的事項(xiàng)提上進(jìn)程了。
本文將簡(jiǎn)述HTTPS及配置數(shù)字證書(shū)的原理并以配置實(shí)例和出現(xiàn)的問(wèn)題進(jìn)行說(shuō)明,希望能對(duì)你提供幫助。(比心~)
HTTPS:
簡(jiǎn)單來(lái)說(shuō),HTTPS就是HTTP協(xié)議上再加一層加密處理的SSL協(xié)議,即HTTP安全版。相比HTTP,HTTPS可以保證內(nèi)容在傳輸過(guò)程中不會(huì)被第三方查看、及時(shí)發(fā)現(xiàn)被第三方篡改的傳輸內(nèi)容、防止身份冒充,從而更有效的保證網(wǎng)絡(luò)數(shù)據(jù)的安全。
HTTPS客戶端與服務(wù)器交互過(guò)程:
1、 客戶端第一次請(qǐng)求時(shí),服務(wù)器會(huì)返回一個(gè)包含公鑰的數(shù)字證書(shū)給客戶端;
2、 客戶端生成對(duì)稱(chēng)加密密鑰并用其得到的公鑰對(duì)其加密后返回給服務(wù)器;
3、 服務(wù)器使用自己私鑰對(duì)收到的加密數(shù)據(jù)解密,得到對(duì)稱(chēng)加密密鑰并保存;
4、 然后雙方通過(guò)對(duì)稱(chēng)加密的數(shù)據(jù)進(jìn)行傳輸。
數(shù)字證書(shū):
在HTTPS客戶端與服務(wù)器第一次交互時(shí),服務(wù)端返回給客戶端的數(shù)字證書(shū)是讓客戶端驗(yàn)證這個(gè)數(shù)字證書(shū)是不是服務(wù)端的,證書(shū)所有者是不是該服務(wù)器,確保數(shù)據(jù)由正確的服務(wù)端發(fā)來(lái),沒(méi)有被第三方篡改。數(shù)字證書(shū)可以保證數(shù)字證書(shū)里的公鑰確實(shí)是這個(gè)證書(shū)的所有者(Subject)的,或者證書(shū)可以用來(lái)確認(rèn)對(duì)方身份。證書(shū)由公鑰、證書(shū)主題(Subject)、數(shù)字簽名(digital signature)等內(nèi)容組成。其中數(shù)字簽名就是證書(shū)的防偽標(biāo)簽,目前使用最廣泛的SHA-RSA加密。
證書(shū)一般分為兩種:
一種是向權(quán)威認(rèn)證機(jī)構(gòu)購(gòu)買(mǎi)的證書(shū),服務(wù)端使用該種證書(shū)時(shí),因?yàn)樘O(píng)果系統(tǒng)內(nèi)置了其受信任的簽名根證書(shū),所以客戶端不需額外的配置。為了證書(shū)安全,在證書(shū)發(fā)布機(jī)構(gòu)公布證書(shū)時(shí),證書(shū)的指紋算法都會(huì)加密后再和證書(shū)放到一起公布以防止他人偽造數(shù)字證書(shū)。而證書(shū)機(jī)構(gòu)使用自己的私鑰對(duì)其指紋算法加密,可以用內(nèi)置在操作系統(tǒng)里的機(jī)構(gòu)簽名根證書(shū)來(lái)解密,以此保證證書(shū)的安全。
另一種是自己制作的證書(shū),即自簽名證書(shū)。好處是不需要花錢(qián)購(gòu)買(mǎi),但使用這種證書(shū)是不會(huì)受信任的,所以需要我們?cè)诖a中將該證書(shū)配置為信任證書(shū)。這就是本文的主要目的。
具體實(shí)現(xiàn)
我們?cè)谑褂米院灻C書(shū)來(lái)實(shí)現(xiàn)HTTPS請(qǐng)求時(shí),因?yàn)椴幌駲C(jī)構(gòu)頒發(fā)的證書(shū)一樣其簽名根證書(shū)在系統(tǒng)中已經(jīng)內(nèi)置了,所以我們需要在App中內(nèi)置自己服務(wù)器的簽名根證書(shū)來(lái)驗(yàn)證數(shù)字證書(shū)。
首先將服務(wù)端生成的.cer格式的根證書(shū)添加到項(xiàng)目中,注意在添加證書(shū)要一定要記得勾選要添加的targets。這里有個(gè)地方要注意:蘋(píng)果的ATS要求服務(wù)端必須支持TLS 1.2或以上版本;必須使用支持前向保密的密碼;證書(shū)必須使用SHA-256或者更好的簽名hash算法來(lái)簽名,如果證書(shū)無(wú)效,則會(huì)導(dǎo)致連接失敗。由于我在生成的根證書(shū)時(shí)簽名hash算法低于其要求,在配置完請(qǐng)求時(shí)一直報(bào)NSURLErrorServerCertificateUntrusted = -1202錯(cuò)誤,希望大家可以注意到這一點(diǎn)。
本文使用AFNetworking 3.0來(lái)配置證書(shū)校驗(yàn)。其中AFSecurityPolicy類(lèi)中封裝了證書(shū)校驗(yàn)的過(guò)程。
AFSecurityPolicy分三種驗(yàn)證模式:
1、AFSSLPinningModeNone:只驗(yàn)證證書(shū)是否在新人列表中
2、AFSSLPinningModeCertificate:驗(yàn)證證書(shū)是否在信任列表中,然后再對(duì)比服務(wù)端證書(shū)和客戶端證書(shū)是否一致
3、 AFSSLPinningModePublicKey:只驗(yàn)證服務(wù)端與客戶端證書(shū)的公鑰是否一致
這里我們選第二種模式,并且對(duì)AFSecurityPolicy的allowInvalidCertificates
和 validatesDomainName
進(jìn)行設(shè)置。
AFSecurityPolicy具體配置代碼如下:
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
securityPolicy.allowInvalidCertificates = YES; //是否允許使用自簽名證書(shū)
securityPolicy.validatesDomainName = NO; //是否需要驗(yàn)證域名
self.manager = [AFHTTPSessionManager manager];
self.manager.responseSerializer = [AFHTTPResponseSerializer serializer];
self.manager.securityPolicy = securityPolicy;
服務(wù)端在接收到客戶端請(qǐng)求時(shí)會(huì)有的情況需要驗(yàn)證客戶端證書(shū),要求客戶端提供合適的證書(shū),再?zèng)Q定是否返回?cái)?shù)據(jù)。這種情況即為質(zhì)詢(challenge)認(rèn)證,雙方進(jìn)行公鑰和私鑰的驗(yàn)證。
為實(shí)現(xiàn)客戶端驗(yàn)證,manager須設(shè)置需要身份驗(yàn)證回調(diào)的方法:
- (void)setSessionDidReceiveAuthenticationChallengeBlock:(NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential * __autoreleasing *credential))block
并實(shí)現(xiàn)具體代碼來(lái)替換AFNetworking的默認(rèn)實(shí)現(xiàn)。其中參數(shù)challenge
為身份驗(yàn)證質(zhì)詢,block返回對(duì)身份驗(yàn)證請(qǐng)求質(zhì)詢的配置。
在接受到質(zhì)詢后,客戶端要根據(jù)服務(wù)端傳來(lái)的challenge來(lái)生成所需的NSURLSessionAuthChallengeDisposition disposition
和NSURLCredential *credential
。disposition指定應(yīng)對(duì)這個(gè)質(zhì)詢的方法,而credential是客戶端生成的質(zhì)詢證書(shū),注意只有challenge中認(rèn)證方法為NSURLAuthenticationMethodServerTrust的時(shí)候,才需要生成挑戰(zhàn)證書(shū)。最后回應(yīng)服務(wù)器的質(zhì)詢。
具體實(shí)現(xiàn)代碼如下:
[self.manager setSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential *__autoreleasing *_credential) {
// 獲取服務(wù)器的trust object
SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust];
//導(dǎo)入自簽名證書(shū)
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"ca" ofType:@"cer"];
NSData* caCert = [NSData dataWithContentsOfFile:cerPath];
NSArray *cerArray = @[caCert];
weakSelf.manager.securityPolicy.pinnedCertificates = cerArray;
SecCertificateRef caRef = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)caCert);
NSCAssert(caRef != nil, @"caRef is nil");
NSArray *caArray = @[(__bridge id)(caRef)];
NSCAssert(caArray != nil, @"caArray is nil");
OSStatus status = SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)caArray);
SecTrustSetAnchorCertificatesOnly(serverTrust,NO);
NSCAssert(errSecSuccess == status, @"SecTrustSetAnchorCertificates failed");
//選擇質(zhì)詢認(rèn)證的處理方式
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__autoreleasing NSURLCredential *credential = nil;
//NSURLAuthenticationMethodServerTrust質(zhì)詢認(rèn)證方式
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
//基于客戶端的安全策略來(lái)決定是否信任該服務(wù)器,不信任則不響應(yīng)質(zhì)詢 。
if ([weakSelf.manager.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
//創(chuàng)建質(zhì)詢證書(shū)
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
//確認(rèn)質(zhì)詢方式
if (credential) {
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {
//取消質(zhì)詢
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
return disposition;
}];
詳細(xì)代碼發(fā)布在了github上,使用時(shí)請(qǐng)注意在ViewController
和NetworkManager
中#Warning
的提示,將證書(shū)拖入項(xiàng)目并在NetworkManager
中添加證書(shū)名稱(chēng),在ViewController
添加自己的URL。
文章中如有錯(cuò)誤和疏漏,歡迎留言指教討論。
參考文章:
數(shù)字證書(shū)原理
Certificate, Key, and Trust Services Tasks for iOS
關(guān)于 iOS 10 中 ATS 的問(wèn)題
App Transport Security(ATS)
AFNetworking之于https認(rèn)證
iOS使用自簽名證書(shū)實(shí)現(xiàn)HTTPS請(qǐng)求