iOS 簡單模擬 https 證書信任邏輯
作者:頭疼腦脹的代碼搬運工
來源:稀土掘金
鏈接:
https://juejin.cn/post/7030345610704191501
開篇:https 證書是什么?如何進(jìn)行認(rèn)證呢?帶著這些疑問來簡單的實現(xiàn)一下驗證過程
簡單的了解一下 https 在數(shù)據(jù)傳輸前的一些操作,如圖:
這里總結(jié)一下上面的流程圖關(guān)鍵的步驟:
1、認(rèn)證網(wǎng)絡(luò)請求的安全性:
服務(wù)器會在建立真正的數(shù)據(jù)傳輸之前返回一個公鑰數(shù)字證書。這里客戶端需要在 URLSession 進(jìn)行認(rèn)證挑戰(zhàn)方法回調(diào)里進(jìn)行判斷然后確定是否要繼續(xù)進(jìn)行請求。代理方法如下:
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
可以這樣理解,URLSession 做 https 網(wǎng)絡(luò)請求的時候其實會把請求鑒權(quán)的權(quán)限通過代理的方法給暴露出來,是否信任并繼續(xù)建立連接可以按照特定規(guī)則去執(zhí)行(如自簽證書),只有 https 請求會走代理方法,http 則不進(jìn)行回調(diào),這也是為什么 iOS系統(tǒng) 為什么提倡使用 https 的原因。
2、認(rèn)證通過,通過公私鑰非對稱加密方式對最后的對稱加密密鑰進(jìn)行加、解密:
這話聽起來有點繞,基于第一步的公鑰數(shù)字證書信任,那么,生成一個用于請求數(shù)據(jù)對稱加密的密鑰(對稱加密更快),用這個公鑰進(jìn)行非對稱加密,在由服務(wù)器的私鑰進(jìn)行解密,得到這個密鑰,那么,真正建立的數(shù)據(jù)傳輸就以此密鑰進(jìn)行加、解密。
下面,模擬一下如何進(jìn)行的公鑰證書受信
創(chuàng)建 公鑰.der 及 證書.cer 文件
在終端依次輸入如下命令:
//生成私鑰
openssl genrsa -out private_key.pem 1024
//獲取 證書.cer
openssl req -new -key private_key.pem -out rsaCertReq.csr
openssl x509 -req -days 3650 -in rsaCertReq.csr -signkey private_key.pem -out rsaCert.crt
//將 .crt 格式證書轉(zhuǎn)換為 .cer 格式證書,后面iOS程序里需要 .cer格式證書
openssl x509 -in rsaCert.crt -out rsaCert.cer -outform der
//獲得 公鑰.der
openssl x509 -outform der -in rsaCert.crt -out public_key.der
過程中會有一些簡單信息輸入,這里沒有特別的要求,文件創(chuàng)建后目錄如圖:
把 .cer 格式證書 和 公鑰.der 格式證書 全部拖到工程里:
下面輸出一段代碼,用 .cer 證書去驗證 公鑰.der 是否可信。
- (void)trustIsVaild
{
//獲取工程下所有cer證書(https 網(wǎng)絡(luò)請求鑒權(quán)必需證書)
NSArray *paths = [[NSBundle mainBundle] pathsForResourcesOfType:@"cer" inDirectory:@"."];
//保存工程內(nèi)的所有 cer 證書(并在后面設(shè)置為鑒權(quán)錨點)
NSMutableArray *pinnedCertificates = [NSMutableArray array];
for (NSString *path in paths) {
NSData *certificateData = [NSData dataWithContentsOfFile:path];
[pinnedCertificates addObject:( __bridge_transfer id)SecCertificateCreateWithData(NULL, ( __bridge CFDataRef)certificateData)];
}
//獲取工程下的公鑰數(shù)字證書(在https網(wǎng)絡(luò)請求認(rèn)證挑戰(zhàn)中由服務(wù)器返回)
NSString * publicKeyPath = [[NSBundle mainBundle] pathForResource:@"public_key" ofType:@"der"];
NSData *derData = [[NSData alloc] initWithContentsOfFile:publicKeyPath];
//證書資源
SecCertificateRef myCertificate = SecCertificateCreateWithData(kCFAllocatorDefault, ( __bridge CFDataRef)derData);
//驗證政策設(shè)置
SecPolicyRef myPolicy = SecPolicyCreateBasicX509();
SecTrustRef myTrust;
//SecTrust 賦值
OSStatus status = SecTrustCreateWithCertificates(myCertificate,myPolicy,&myTrust);
if (status == noErr) {
//設(shè)置證書錨點(這里的意思就是如果鑒權(quán)到指定的證書是有效的,那么,就信任此公鑰數(shù)字簽名,這里如果不設(shè)置,那么就會一直找向根證書,由于工程里的公鑰數(shù)字證書是自簽的,所以,一定不會受信)
SecTrustSetAnchorCertificates(myTrust, ( __bridge CFArrayRef)pinnedCertificates);
SecTrustResultType result;
if (SecTrustEvaluate(myTrust, &result) == 0) {
//kSecTrustResultUnspecified 隱式信任
//kSecTrustResultProceed 可繼續(xù)進(jìn)行
if ((result == kSecTrustResultUnspecified || result == kSecTrustResultProceed)) {
NSLog(@"受信任的證書");
} else {
NSLog(@"未受信任的證書");
}
} else {
NSLog(@"未受信任的證書初始化操作失敗");
}
}
}
運行如下:
順便輸出一下不設(shè)置 證書錨點 控制臺內(nèi)容:
if (status == noErr) {
//不設(shè)置錨點
//SecTrustSetAnchorCertificates(myTrust, (__bridge CFArrayRef)pinnedCertificates);
SecTrustResultType result;
if (SecTrustEvaluate(myTrust, &result) == 0) {
if ((result == kSecTrustResultUnspecified || result == kSecTrustResultProceed)) {
NSLog(@"受信任的證書");
} else {
NSLog(@"未受信任的證書");
}
} else {
NSLog(@"未受信任的證書初始化操作失敗");
}
}
到這里,公鑰證書如果受信,那么,下一步就規(guī)定一個 對稱加密 session key 用這個公鑰加密,發(fā)送到服務(wù)器,然后用對應(yīng)的私鑰解密,供以后的數(shù)據(jù)傳輸進(jìn)行 對稱加密 操作。
所以,移動端在做自定義證書鑒權(quán)的時候就需要存儲服務(wù)器生成的 .cer 證書文件!
AFNetworking 下的鑒權(quán)方式處理相對復(fù)雜,因為 URLSession 的認(rèn)證挑戰(zhàn)回調(diào)是允許程序員全部無條件開啟的,所以,AFNetworking 在默認(rèn)鑒權(quán)行為的基礎(chǔ)上添加了幾種自定義鑒權(quán)方式:
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
AFSSLPinningModeNone,//無條件開啟
AFSSLPinningModePublicKey,//認(rèn)證公鑰內(nèi)容
AFSSLPinningModeCertificate,//認(rèn)證證書
};
而且,在此之前 AFNetworking 通過
@property (readwrite, nonatomic, copy) AFURLSessionTaskAuthenticationChallengeBlock authenticationChallengeHandler;
暴露給外界閉包進(jìn)行自定義鑒權(quán)邏輯及處理結(jié)果。
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
BOOL evaluateServerTrust = NO;
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
NSURLCredential *credential = nil;
//AFNetworking 暴露給程序員自定義處理入口
if (self.authenticationChallengeHandler) {
id result = self.authenticationChallengeHandler(....);
... (解析處理結(jié)果)
}
...(證書認(rèn)證處理代碼)
//最后調(diào)用 completionHandler 繼續(xù)執(zhí)行操作
if (completionHandler) {
completionHandler(disposition, credential);
}
}
disposition: 可以設(shè)置繼續(xù)鑒權(quán)挑戰(zhàn)(NSURLSessionAuthChallengeUseCredential) 或者中斷鑒權(quán)挑戰(zhàn)(NSURLSessionAuthChallengeCancelAuthenticationChallenge)
credential: 如果證書認(rèn)證通過則直接進(jìn)行賦值,
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
否則為 nil
這里只是簡單的梳理一下證書信任邏輯,就不再贅述 AFNetworking 源碼部分。
作者:頭疼腦脹的代碼搬運工
來源:稀土掘金
鏈接:
https://juejin.cn/post/7030345610704191501
-End-
最近有一些小伙伴,讓我?guī)兔φ乙恍?nbsp;面試題 資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來,可以說是程序員面試必備!所有資料都整理到網(wǎng)盤了,歡迎下載!

面試題】即可獲取
