前三篇加密和哈希瘪弓、數(shù)字簽名和數(shù)字證書、HTTPS的核心SSL/TLS協(xié)議已經(jīng)把相關(guān)原理說完了房午,具體理解還要和實(shí)際使用結(jié)合起來。本人從事iOS
開發(fā)竞膳,這里主要講述在iOS
中的應(yīng)用。而在iOS
中大部分網(wǎng)絡(luò)請求都是使用的AFNetworking
這個(gè)第三方庫诫硕,而它又是基于NSURLSession
的封裝坦辟,所以此文也會(huì)從這兩個(gè)方面進(jìn)行講解。由于NSURLConnection
基本已經(jīng)無人使用章办,這個(gè)就不在提了锉走,大致使用和NSURLSession
類似。
此篇文章的邏輯圖
概述
無論是
AFNetworking
還是NSURLSession
纲菌,整個(gè)HTTPS
協(xié)議的傳輸挠日,都要經(jīng)過上文中提到的SSL/TLS
的握手階段。創(chuàng)建一個(gè)請求翰舌,開始請求的時(shí)候嚣潜,開始第一個(gè)階段Client Hello
,然后服務(wù)端Server Hello
階段回應(yīng)客戶端椅贱,會(huì)到調(diào)用到NSURLSessionDelegate
的兩個(gè)方法懂算,而客戶端在代理方法中處理SSL/TLS
的第三個(gè)階段最后客戶端回應(yīng)服務(wù)端,然后服務(wù)端在驗(yàn)證庇麦,從建立起SSL/TLS
的連接计技。而這四個(gè)過程中,服務(wù)端程序員主要操作對應(yīng)第二步山橄,客戶端程序員主要操作對應(yīng)第三步垮媒,而第三步里面主要操作的就是驗(yàn)證證書。其他的一些航棱,像產(chǎn)生隨機(jī)數(shù)等睡雇,并不需要程序員操作,相關(guān)底層都已經(jīng)封裝好了饮醇。根這過程下面分別介紹NSURLSession
和AFNetworking
是如何支持HTTPS
的它抱。
NSURLSession支持HTTPS
更正:證書在系統(tǒng)默認(rèn)信任列表中,則可以直接按HTTP方式請求朴艰,一般不需要再寫下面的驗(yàn)證代碼观蓄。
證書在系統(tǒng)默認(rèn)信任列表中和不在列表中(自建證書)的驗(yàn)證方式不同混移,下面分別敘述。
證書在系統(tǒng)默認(rèn)信任列表中的驗(yàn)證
// 創(chuàng)建一個(gè)HTTPS請求侮穿,這步包括了SSL/TLS握手協(xié)議的第一步Client Hello
NSURL *url = [NSURL URLWithString:@"https://www.baidu.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request];
[task resume];
NSURLSession
對證書的認(rèn)證有兩個(gè)代理方法歌径,Server Hello
階段后會(huì)來到這兩個(gè)代理方法中的其中一個(gè),SSL/TLS
第三個(gè)階段主要就在這兒實(shí)現(xiàn)亲茅。下面以其中一個(gè)會(huì)話級別的回調(diào)為例說明沮脖。
// 會(huì)話級別的回調(diào)
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler
// 任務(wù)級別的回調(diào)
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler
兩個(gè)代理方法的區(qū)別就是第二個(gè)回調(diào)多了一個(gè)task,
如果同時(shí)實(shí)現(xiàn)兩個(gè)代理方法芯急,則有回調(diào)優(yōu)先級別更高的會(huì)話代理方法。
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler {
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
// 1. 先判斷服務(wù)器采用的認(rèn)證方法是否為NSURLAuthenticationMethodServerTrust驶俊,ServerTrust是比較常用的娶耍,當(dāng)然還有其他的認(rèn)證方法。
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
// 2. 獲取需要驗(yàn)證的信任對象饼酿,并采用系統(tǒng)默認(rèn)驗(yàn)證方式SecTrustEvaluate進(jìn)行驗(yàn)證榕酒,其中驗(yàn)證的API在Security庫中
SecTrustRef trust = challenge.protectionSpace.serverTrust;
SecTrustResultType result;
OSStatus status = SecTrustEvaluate(trust, &result);
if (status == errSecSuccess && (result == kSecTrustResultProceed || result == kSecTrustResultUnspecified)) {
// 3. 驗(yàn)證成功,根據(jù)服務(wù)器返回的受保護(hù)空間中的信任對象故俐,創(chuàng)建一個(gè)挑戰(zhàn)憑證想鹰,并且挑戰(zhàn)方式為使用憑證挑戰(zhàn)
credential = [NSURLCredential credentialForTrust:trust];
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
// 3. 驗(yàn)證失敗,取消本次挑戰(zhàn)認(rèn)證
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
// 1. 如果服務(wù)器采用的認(rèn)證方法不是ServerTrust药版,可判斷是否為其他認(rèn)證辑舷,如何NSURLAuthenticationMethodHTTPDigest,等等槽片,這里我沒有判斷何缓,直接處理為系統(tǒng)默認(rèn)處理。
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
// 4. 無論結(jié)果如何还栓,都要回到給服務(wù)端
completionHandler(disposition, credential);
}
自建證書的驗(yàn)證
由于自建證書并沒有在系統(tǒng)默認(rèn)的證書信任列表中碌廓,如果使用默認(rèn)驗(yàn)證方法是不會(huì)通過的,這時(shí)候就要App
提前置入證書剩盒。
更新:但是如果ATS
的Allow Arbitrary Loads
配置為NO
谷婆,則無法通過驗(yàn)證。(2016-12-22)
// 先導(dǎo)入證書
// 證書的路徑
NSString *cerPath = ...;
NSData *cerData = [NSData dataWithContentsOfFile:cerPath];
SecCertificateRef certificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)(cerData));
// 自建信任證書列表
self.trustedCertificates = @[CFBridgingRelease(certificate)];
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler {
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
// 1. 先判斷服務(wù)器采用的認(rèn)證方法是否為NSURLAuthenticationMethodServerTrust辽聊,ServerTrust是比較常用的纪挎,當(dāng)然還有其他的認(rèn)證方法。
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
// 2. 獲取需要驗(yàn)證的信任對象身隐,并設(shè)置信任對象要驗(yàn)證的證書為之前導(dǎo)入的證書廷区,在SecTrustEvaluate進(jìn)行驗(yàn)證,其中驗(yàn)證的API在Security庫中
SecTrustRef trust = challenge.protectionSpace.serverTrust;
SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)self.trustedCertificates);
SecTrustResultType result;
OSStatus status = SecTrustEvaluate(trust, &result);
if (status == errSecSuccess && (result == kSecTrustResultProceed || result == kSecTrustResultUnspecified)) {
// 3. 驗(yàn)證成功贾铝,根據(jù)服務(wù)器返回的受保護(hù)空間中的信任對象隙轻,創(chuàng)建一個(gè)挑戰(zhàn)憑證埠帕,并且挑戰(zhàn)方式為使用憑證挑戰(zhàn)
credential = [NSURLCredential credentialForTrust:trust];
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
// 3. 驗(yàn)證失敗,取消本次挑戰(zhàn)認(rèn)證
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
// 1. 如果服務(wù)器采用的認(rèn)證方法不是ServerTrust玖绿,可判斷是否為其他認(rèn)證敛瓷,如何NSURLAuthenticationMethodHTTPDigest,等等斑匪,這里我沒有判斷呐籽,直接處理為系統(tǒng)默認(rèn)處理。
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
// 4. 無論結(jié)果如何蚀瘸,都要回到給服務(wù)端
completionHandler(disposition, credential);
}
AFNetworking支持HTTPS
概述
AFNetworking
支持HTTPS
相關(guān)的類為AFSecurityPolicy
狡蝶,其中AFNetworking
提供了三種驗(yàn)證方式:
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
AFSSLPinningModeNone, // 是默認(rèn)的認(rèn)證方式,只會(huì)在系統(tǒng)的信任的證書列表中對服務(wù)端返回的證書進(jìn)行驗(yàn)證
AFSSLPinningModePublicKey, // 需要預(yù)先保存服務(wù)端發(fā)送的證書(自建證書)贮勃,但是這里只會(huì)驗(yàn)證證書中的公鑰是否正確
AFSSLPinningModeCertificate, // 需要客戶端預(yù)先保存服務(wù)端的證書(自建證書)
};
// AFSecurityPolicy相關(guān)屬性
// 驗(yàn)證方式贪惹,對應(yīng)上面的三種驗(yàn)證方式
@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode;
// 自建證書的時(shí)候,提供相應(yīng)的證書
@property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates;
// 是否允許自建證書
@property (nonatomic, assign) BOOL allowInvalidCertificates;
// 是否需要驗(yàn)證域名
@property (nonatomic, assign) BOOL validatesDomainName;
--------------------------------------
// AFSecurityPolicy相關(guān)方法
// 驗(yàn)證邏輯寂嘉,這個(gè)方法在AFURLSessionManager這個(gè)類中奏瞬,實(shí)現(xiàn)的NSURLSession的兩個(gè)代理方法中調(diào)用。
// 具體驗(yàn)證邏輯泉孩,大家可讀AFNetworking源碼
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(nullable NSString *)domain;
實(shí)際運(yùn)用
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
AFSecurityPolicy *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
policy.allowInvalidCertificates = YES;
policy.validatesDomainName = YES;
// 證書的路徑
NSString *cerPath = ...;
NSData *cerData = [NSData dataWithContentsOfFile:cerPath];
SecCertificateRef certificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)(cerData));
policy.pinnedCertificates = [NSSet setWithObject:CFBridgingRelease(certificate)];
manager.securityPolicy = policy;
// 下面使用manager開始請求硼端,由于AFNetworking的高度封裝,使用起來及其方便寓搬。
總結(jié)
關(guān)于AFNetworking
并沒有提及太多珍昨,相關(guān)部分可讀一下源碼,看一下此文并沒有提到的自建證書公鑰驗(yàn)證句喷,其實(shí)就是從證書中提取出來公鑰曼尊。另外也可大致瀏覽一下Security
這個(gè)Framework
,你會(huì)發(fā)現(xiàn)不少原理中提到的知識脏嚷,比如<Security/CipherSuite.h>
里面的加密組件骆撇,特別是<Security/SecureTransport.h>
中定義的OSStatus
狀態(tài)碼非常有助于調(diào)試,還有SSL/TLS
版本父叙,以及文章中從沒有提過的X.509標(biāo)準(zhǔn)神郊。到此HTTPS
的基本原理和使用交代完畢,讀完此系列文章趾唱,會(huì)對HTTPS
有個(gè)詳細(xì)的了解涌乳。但是HTTPS
也不是絕對安全的,還是會(huì)有很多的攻擊方法甜癞。有時(shí)間會(huì)再出一篇文章來專門講解HTTPS
的攻防夕晓。文章從最基礎(chǔ)的講起,分了四篇來寫悠咱,也是比較長的蒸辆;感謝大家耐心看完征炼,對于文章不足之處,敬請包含躬贡,也請多提意見谆奥,共同進(jìn)步。