在16年的WWDC中,Apple已表示將從2017年1月1日起,所有新提交的App必須強制性應用HTTPS協議來進行網絡請求。默認情況下非HTTPS的網絡訪問是禁止的並且不能再通過簡單粗暴的向Info.plist中添加NSAllowsArbitraryLoads設置繞過ATS(App Transport Security)的限制(否則須在應用審核時進行說明並很可能會被拒)。所以還未進行相應配置的公司需要盡快將升級為HTTPS的事項提上進程了。
本文將簡述HTTPS及配置數字證書的原理並以配置實例和出現的問題進行說明,希望能對你提供幫助。
HTTPS:
簡單來說,HTTPS就是HTTP協議上再加一層加密處理的SSL協議,即HTTP安全版。相比HTTP,HTTPS可以保證內容在傳輸過程中不會被第三方查看、及時發現被第三方篡改的傳輸內容、防止身份冒充,從而更有效的保證網絡數據的安全。
HTTPS客戶端與服務器交互過程:
1、 客戶端第一次請求時,服務器會返回一個包含公鑰的數字證書給客戶端;
2、 客戶端生成對稱加密密鑰並用其得到的公鑰對其加密后返回給服務器;
3、 服務器使用自己私鑰對收到的加密數據解密,得到對稱加密密鑰並保存;
4、 然后雙方通過對稱加密的數據進行傳輸。
數字證書:
在HTTPS客戶端與服務器第一次交互時,服務端返回給客戶端的數字證書是讓客戶端驗證這個數字證書是不是服務端的,證書所有者是不是該服務器,確保數據由正確的服務端發來,沒有被第三方篡改。數字證書可以保證數字證書里的公鑰確實是這個證書的所有者(Subject)的,或者證書可以用來確認對方身份。證書由公鑰、證書主題(Subject)、數字簽名(digital signature)等內容組成。其中數字簽名就是證書的防偽標簽,目前使用最廣泛的SHA-RSA加密。
證書一般分為兩種:
一種是向權威認證機構購買的證書,服務端使用該種證書時,因為蘋果系統內置了其受信任的簽名根證書,所以客戶端不需額外的配置。為了證書安全,在證書發布機構公布證書時,證書的指紋算法都會加密后再和證書放到一起公布以防止他人偽造數字證書。而證書機構使用自己的私鑰對其指紋算法加密,可以用內置在操作系統里的機構簽名根證書來解密,以此保證證書的安全。
另一種是自己制作的證書,即自簽名證書。好處是不需要花錢購買,但使用這種證書是不會受信任的,所以需要我們在代碼中將該證書配置為信任證書。這就是本文的主要目的。
具體實現
我們在使用自簽名證書來實現HTTPS請求時,因為不像機構頒發的證書一樣其簽名根證書在系統中已經內置了,所以我們需要在App中內置自己服務器的簽名根證書來驗證數字證書。
首先將服務端生成的.cer格式的根證書添加到項目中,注意在添加證書要一定要記得勾選要添加的targets。這里有個地方要注意:蘋果的ATS要求服務端必須支持TLS 1.2或以上版本;必須使用支持前向保密的密碼;證書必須使用SHA-256或者更好的簽名hash算法來簽名,如果證書無效,則會導致連接失敗。由於我在生成的根證書時簽名hash算法低於其要求,在配置完請求時一直報NSURLErrorServerCertificateUntrusted = -1202錯誤,希望大家可以注意到這一點。
本文使用AFNetworking 3.0來配置證書校驗。其中AFSecurityPolicy類中封裝了證書校驗的過程。
AFSecurityPolicy分三種驗證模式:
1、AFSSLPinningModeNone:只驗證證書是否在新人列表中
2、AFSSLPinningModeCertificate:驗證證書是否在信任列表中,然后再對比服務端證書和客戶端證書是否一致
3、 AFSSLPinningModePublicKey:只驗證服務端與客戶端證書的公鑰是否一致
這里我們選第二種模式,並且對AFSecurityPolicy的allowInvalidCertificates和 validatesDomainName進行設置。
AFSecurityPolicy具體配置代碼如下:
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate]; securityPolicy.allowInvalidCertificates = YES; //是否允許使用自簽名證書 securityPolicy.validatesDomainName = NO; //是否需要驗證域名 self.manager = [AFHTTPSessionManager manager]; self.manager.responseSerializer = [AFHTTPResponseSerializer serializer]; self.manager.securityPolicy = securityPolicy;
服務端在接收到客戶端請求時會有的情況需要驗證客戶端證書,要求客戶端提供合適的證書,再決定是否返回數據。這種情況即為質詢(challenge)認證,雙方進行公鑰和私鑰的驗證。
為實現客戶端驗證,manager須設置需要身份驗證回調的方法:
- (void)setSessionDidReceiveAuthenticationChallengeBlock:(NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential * __autoreleasing *credential))block
並實現具體代碼來替換AFNetworking的默認實現。其中參數challenge為身份驗證質詢,block返回對身份驗證請求質詢的配置。
在接受到質詢后,客戶端要根據服務端傳來的challenge來生成所需的NSURLSessionAuthChallengeDisposition disposition和NSURLCredential *credential。disposition指定應對這個質詢的方法,而credential是客戶端生成的質詢證書,注意只有challenge中認證方法為NSURLAuthenticationMethodServerTrust的時候,才需要生成挑戰證書。最后回應服務器的質詢。
具體實現代碼如下:
[self.manager setSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential *__autoreleasing *_credential) { // 獲取服務器的trust object SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust]; //導入自簽名證書 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"); //選擇質詢認證的處理方式 NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling; __autoreleasing NSURLCredential *credential = nil; //NSURLAuthenticationMethodServerTrust質詢認證方式 if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { //基於客戶端的安全策略來決定是否信任該服務器,不信任則不響應質詢 。 if ([weakSelf.manager.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.hos t]) { //創建質詢證書 credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; //確認質詢方式 if (credential) { disposition = NSURLSessionAuthChallengeUseCredential; } else { disposition = NSURLSessionAuthChallengePerformDefaultHandling; } } else { //取消質詢 disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge; } } else { disposition = NSURLSessionAuthChallengePerformDefaultHandling; } return disposition; }];
詳細代碼發布在了github上,使用時請注意在ViewController
和NetworkManager
中#Warning
的提示,將證書拖入項目並在NetworkManager
中添加證書名稱,在ViewController
添加自己的URL。
參考文章:
Certificate, Key, and Trust Services Tasks for iOS
關於 iOS 10 中 ATS 的問題
App Transport Security(ATS)
數字證書原理
iOS使用自簽名證書實現HTTPS請求