一剿骨、SSL Pinning 簡(jiǎn)介
1覆旭、使用背景
在開(kāi)發(fā)手機(jī)應(yīng)用時(shí)铸屉,如何正確的使用HTTPS來(lái)提高網(wǎng)絡(luò)傳輸?shù)陌踩允怯葹橹匾亩て选TTPS協(xié)議本使用了SSL 加密傳輸,相比HTTP但依然存在極大的安全隱患----中間人攻擊彻坛。SSL解決了內(nèi)容的加密的問(wèn)題顷啼,但是SSL過(guò)程中是依靠證書(shū)進(jìn)行驗(yàn)證的,這就需要保證證書(shū)絕對(duì)的安全昌屉。先立一個(gè)小目標(biāo)(偽造證書(shū))钙蒙,萬(wàn)一實(shí)現(xiàn)了呢?在立一個(gè)小目標(biāo)(偽造服務(wù)器)间驮,萬(wàn)一實(shí)現(xiàn)了呢躬厌?事實(shí)證明目標(biāo)是可以實(shí)現(xiàn)的(SSL系統(tǒng)遭入侵發(fā)布虛假密鑰 微軟谷歌受影響 )。SSL Pinning技術(shù)就是基于SSL基礎(chǔ)上在添加一個(gè)本地證書(shū)竞帽,用來(lái)再次驗(yàn)證扛施!
2、中間人攻擊
中間人攻擊(Man-in-the-middle Attack抢呆,簡(jiǎn)稱(chēng)MITM煮嫌、MitM、MIM抱虐、MiM昌阿、MITMA)是一種由來(lái)已久的網(wǎng)絡(luò)入侵手段,并且在今天仍然有著廣泛的發(fā)展空間恳邀,如SMB會(huì)話劫持懦冰、DNS欺騙等攻擊都是典型的中間人攻擊。簡(jiǎn)而言之谣沸,所謂的中間人攻擊就是通過(guò)攔截正常的網(wǎng)絡(luò)通信數(shù)據(jù)刷钢,并進(jìn)行數(shù)據(jù)篡改和嗅探,而通信的雙方卻毫不知情乳附。
3内地、Charles抓包原理
Charles作為一個(gè)中間人代理,當(dāng)瀏覽器和服務(wù)器通信時(shí)赋除,Charles接收服務(wù)器的證書(shū)阱缓,但動(dòng)態(tài)生成一張證書(shū)發(fā)送給瀏覽器,也就是說(shuō)Charles作為中間代理在瀏覽器和服務(wù)器之間通信举农,所以通信的數(shù)據(jù)可以被Charles攔截并解密荆针。由于Charles更改了證書(shū),瀏覽器校驗(yàn)不通過(guò)會(huì)給出安全警告,必須安裝Charles的證書(shū)后才能進(jìn)行正常訪問(wèn)航背。
- 客戶(hù)端向服務(wù)器發(fā)起HTTPS請(qǐng)求
- Charles攔截客戶(hù)端的請(qǐng)求喉悴,偽裝成客戶(hù)端向服務(wù)器進(jìn)行請(qǐng)求
- 服務(wù)器向“客戶(hù)端”(實(shí)際上是Charles)返回服務(wù)器的CA證書(shū)
- Charles攔截服務(wù)器的響應(yīng),獲取服務(wù)器證書(shū)公鑰玖媚,然后自己制作一張證書(shū)箕肃,將服務(wù)器證書(shū)替換后發(fā)送給客戶(hù)端。(這一步最盅,Charles拿到了服務(wù)器證書(shū)的公鑰)
- 客戶(hù)端接收到“服務(wù)器”(實(shí)際上是Charles)的證書(shū)后突雪,生成一個(gè)對(duì)稱(chēng)密鑰,用Charles的公鑰加密涡贱,發(fā)送給“服務(wù)器”(Charles)
- Charles攔截客戶(hù)端的響應(yīng)咏删,用自己的私鑰解密對(duì)稱(chēng)密鑰,然后用服務(wù)器證書(shū)公鑰加密问词,發(fā)送給服務(wù)器督函。(這一步,Charles拿到了對(duì)稱(chēng)密鑰)
- 服務(wù)器用自己的私鑰解密對(duì)稱(chēng)密鑰激挪,向“客戶(hù)端”(Charles)發(fā)送響應(yīng)
- Charles攔截服務(wù)器的響應(yīng)辰狡,替換成自己的證書(shū)后發(fā)送給客戶(hù)端
- 至此,連接建立垄分,Charles拿到了 服務(wù)器證書(shū)的公鑰 和 客戶(hù)端與服務(wù)器協(xié)商的對(duì)稱(chēng)密鑰宛篇,之后就可以解密或者修改加密的報(bào)文了。
HTTPS抓包的原理還是挺簡(jiǎn)單的薄湿,簡(jiǎn)單來(lái)說(shuō)叫倍,就是Charles作為“中間人代理”,拿到了 服務(wù)器證書(shū)公鑰 和 HTTPS連接的對(duì)稱(chēng)密鑰豺瘤,前提是客戶(hù)端選擇信任并安裝Charles的CA證書(shū)吆倦,否則客戶(hù)端就會(huì)“報(bào)警”并中止連接。這樣看來(lái)坐求,HTTPS還是很安全的蚕泽。
4、SSL Pinning
SSL Pinning(又叫Certificate Pinning)可以理解為證書(shū)綁定桥嗤。在一些應(yīng)用場(chǎng)景中须妻,客戶(hù)端和服務(wù)器之間的通信是事先約定好的,既服務(wù)器地址和證書(shū)是預(yù)先知道的泛领,這種情況常見(jiàn)于CS(Client-Server)架構(gòu)的應(yīng)用中荒吏。這樣的話在客戶(hù)端事先保存好一份服務(wù)器的證書(shū)(含公鑰),每次請(qǐng)求服務(wù)器的時(shí)候师逸,將服務(wù)器返回的證書(shū)與客戶(hù)端保存的證書(shū)進(jìn)行對(duì)比司倚,如果證書(shū)不符,說(shuō)明受到中間人攻擊篓像,馬上可以中斷請(qǐng)求动知。這樣的話中間人就無(wú)法偽造證書(shū)進(jìn)行攻擊了。
我們需要將APP代碼內(nèi)置僅接受指定域名的證書(shū)员辩,而不接受操作系統(tǒng)或?yàn)g覽器內(nèi)置的CA根證書(shū)對(duì)應(yīng)的任何證書(shū)盒粮,通過(guò)這種授權(quán)方式,保障了APP與服務(wù)端通信的唯一性和安全性奠滑。但是CA簽發(fā)證書(shū)都存在有效期問(wèn)題丹皱,所以缺點(diǎn)是在證書(shū)續(xù)期后需要將證書(shū)重新內(nèi)置到APP中。
公鑰鎖定則是提取證書(shū)中的公鑰并內(nèi)置到移動(dòng)端APP中宋税,通過(guò)與服務(wù)器對(duì)比公鑰值來(lái)驗(yàn)證連接的合法性摊崭,我們?cè)谥谱髯C書(shū)密鑰時(shí),公鑰在證書(shū)的續(xù)期前后都可以保持不變(即密鑰對(duì)不變)杰赛,所以可以避免證書(shū)有效期問(wèn)題呢簸。
證書(shū)鎖定旨在解決移動(dòng)端APP與服務(wù)端通信的唯一性,實(shí)際通信過(guò)程中乏屯,如果鎖定過(guò)程失敗根时,那么客戶(hù)端APP將拒絕針對(duì)服務(wù)器的所有 SSL/TLS 請(qǐng)求,F(xiàn)aceBook/Twitter則通過(guò)證書(shū)鎖定以防止Charles/Fiddler等抓包工具中間人攻擊辰晕。
為什么直接對(duì)比就能保證證書(shū)沒(méi)問(wèn)題蛤迎?如果中間人從客戶(hù)端取出證書(shū),再偽裝成服務(wù)端跟其他客戶(hù)端通信含友,它發(fā)送給客戶(hù)端的這個(gè)證書(shū)不就能通過(guò)驗(yàn)證嗎替裆?確實(shí)可以通過(guò)驗(yàn)證,但后續(xù)的流程走不下去唱较,因?yàn)橄乱徊娇蛻?hù)端會(huì)用證書(shū)里的公鑰加密扎唾,中間人沒(méi)有這個(gè)證書(shū)的私鑰就解不出內(nèi)容,也就截獲不到數(shù)據(jù)南缓,這個(gè)證書(shū)的私鑰只有真正的服務(wù)端有胸遇,中間人偽造證書(shū)主要偽造的是公鑰。
為什么要用SSL Pinning汉形?正常的驗(yàn)證方式不夠嗎纸镊?如果服務(wù)端的證書(shū)是從受信任的的CA機(jī)構(gòu)頒發(fā)的,驗(yàn)證是沒(méi)問(wèn)題的概疆,但CA機(jī)構(gòu)頒發(fā)證書(shū)比較昂貴逗威,小企業(yè)或個(gè)人用戶(hù)可能會(huì)選擇自己頒發(fā)證書(shū),這樣就無(wú)法通過(guò)系統(tǒng)受信任的CA機(jī)構(gòu)列表驗(yàn)證這個(gè)證書(shū)的真?zhèn)瘟瞬砑剑孕枰猄SL Pinning這樣的方式去驗(yàn)證凯旭。
二、NSURLSession方式
1、獲取證書(shū)
客戶(hù)端需要證書(shū)(Certification file)罐呼, .cer格式的文件鞠柄。可以跟服務(wù)器端索取嫉柴。如果他們給個(gè).pem文件厌杜,要使用命令行轉(zhuǎn)換:
openssl x509 -inform PEM -in name.pem -outform DER -out name.cer
如果給了個(gè).crt文件,請(qǐng)這樣轉(zhuǎn)換:
openssl x509 -in name.crt -out name.cer -outform der
如果啥都不給你计螺,你只能自己動(dòng)手了夯尽,這里以github.com
為例子,獲取證書(shū):
openssl s_client -connect github.com:443 </dev/null 2>/dev/null | openssl x509 -outform DER > github.com.cer
2登馒、NSURLSession實(shí)現(xiàn)
當(dāng)談到NSURLSession使用SSL pinning有點(diǎn)棘手匙握,因?yàn)樵贏FNetworking中,其本身已經(jīng)有封裝好的類(lèi)可以使用來(lái)進(jìn)行配置陈轿。這里沒(méi)有辦法去設(shè)置一組證書(shū)來(lái)自動(dòng)取消所有本地證書(shū)不匹配的response肺孤。我們需要手動(dòng)執(zhí)行檢查來(lái)實(shí)現(xiàn)在NSURLSession上的SSL pinning。我們很榮幸的是我們可以用Security's framework C API济欢。
創(chuàng)建默認(rèn)會(huì)話配置的NSURLSession對(duì)象赠堵,及發(fā)送請(qǐng)求,執(zhí)行任務(wù)
// 設(shè)置地址
NSURL *testURL = [NSURL URLWithString:@"https://github.com"];
// 創(chuàng)建默認(rèn)會(huì)話配置的NSURLSession對(duì)象
NSURLSessionConfiguration *seeConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
seeConfig.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
NSURLSession *session = [NSURLSession sessionWithConfiguration:seeConfig
delegate:self
delegateQueue:nil];
// NSURLSession使用NSURLSessionTask來(lái)發(fā)送一個(gè)請(qǐng)求法褥,
// 我們使用dataTaskWithURL:completionHandler:方法來(lái)進(jìn)行SSL pinning 測(cè)試
NSURLSessionDataTask *task =
[session dataTaskWithURL:testURL
completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (!error) {
NSString *str =
[[NSString alloc]initWithData:data encoding:NSASCIIStringEncoding];
NSLog(@"str : %@", str);
} else {
NSLog(@"error : %@", error);
}
}];
[task resume];
在代理回調(diào)方法中茫叭,校驗(yàn)證書(shū)是否合法
// 代理回調(diào)
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler {
// 得到遠(yuǎn)程證書(shū)
SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, 0);
// 設(shè)置ssl政策來(lái)檢測(cè)主域名
NSMutableArray *policies = [NSMutableArray array];
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)challenge.protectionSpace.host)];
// 驗(yàn)證服務(wù)器證書(shū)
SecTrustResultType result;
SecTrustEvaluate(serverTrust, &result);
BOOL certificateIsValid =
(result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
// 得到遠(yuǎn)程和本地證書(shū)data
NSData *remoteCertificateData = CFBridgingRelease(SecCertificateCopyData(certificate));
NSString *pathToCert = [[NSBundle mainBundle] pathForResource:@"github2018" ofType:@"cer"];
NSData *localCertificate = [NSData dataWithContentsOfFile:pathToCert];
// 檢查
if (certificateIsValid && [remoteCertificateData isEqualToData:localCertificate]) {
// 驗(yàn)證通過(guò)
NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
}else {
// 驗(yàn)證不通過(guò)
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge,NULL);
}
}
上述方法的開(kāi)始,我們使用SecTrustGetCertificateAtIndex
來(lái)得到服務(wù)器的SSL證書(shū)數(shù)據(jù)半等。然后使用證書(shū)評(píng)估設(shè)置policies揍愁。證書(shū)使用SecTrustEvaluate
評(píng)估,然后返回以下幾種認(rèn)證結(jié)果類(lèi)型之一:
typedef uint32_t SecTrustResultType;
enum {
kSecTrustResultInvalid = 0,
kSecTrustResultProceed = 1,
kSecTrustResultConfirm SEC_DEPRECATED_ATTRIBUTE = 2,
kSecTrustResultDeny = 3,
kSecTrustResultUnspecified = 4,
kSecTrustResultRecoverableTrustFailure = 5,
kSecTrustResultFatalTrustFailure = 6,
kSecTrustResultOtherError = 7
};
如果我們得到kSecTrustResultProceed
和kSecTrustResultUnspecified
之外的類(lèi)型結(jié)果杀饵,我們可以認(rèn)為證書(shū)是無(wú)效的(不被信任的)莽囤。
至今為止我們除了檢測(cè)遠(yuǎn)程服務(wù)器證書(shū)評(píng)估外,還沒(méi)有做其他事情切距,對(duì)于SSL pinning 檢測(cè)我們需要通過(guò)SecCertificateRef
來(lái)得到他的NSData朽缎。這個(gè)SecCertificateRef
來(lái)自于challenge.protectionSpace.serverTrust
。而本地的NSData來(lái)自本地的.cer
證書(shū)文件谜悟。然后我們使用isEqual來(lái)進(jìn)行SSL pinning话肖。
如果遠(yuǎn)程服務(wù)器證書(shū)的NSData等于本地的證書(shū)data,那么就可以通過(guò)評(píng)估葡幸,我們可以驗(yàn)證服務(wù)器身份然后進(jìn)行通信最筒,而且還要使用completionHandler(NSURLSessionAuthChallengeUseCredential,credential)
執(zhí)行request。
然而如果兩個(gè)data不相等蔚叨,我們使用completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge,NULL)
方法來(lái)取消dataTask的執(zhí)行床蜘,這樣就可以拒絕和服務(wù)器溝通辙培。
這就是在NSURLSession中使用SSL pinning。
三邢锯、AFNetworking方式
1虏冻、AFSecurityPolicy
安全模式設(shè)置
AFSecurityPolicy是AFNetworking中三種安全策略模塊,提供了證書(shū)鎖定模式
- AFSSLPinningModeNone:
這個(gè)模式表示不做SSL pinning弹囚,只跟瀏覽器一樣在系統(tǒng)的信任機(jī)構(gòu)列表里驗(yàn)證服務(wù)端返回的證書(shū)。
若證書(shū)是信任機(jī)構(gòu)簽發(fā)的就會(huì)通過(guò)领曼,若是自己服務(wù)器生成的證書(shū)鸥鹉,這里是不會(huì)通過(guò)的。
- AFSSLPinningModeCertificate:
這個(gè)模式表示用證書(shū)綁定方式驗(yàn)證證書(shū)庶骄,需要客戶(hù)端保存有服務(wù)端的證書(shū)拷貝毁渗,
這里驗(yàn)證分兩步,第一步驗(yàn)證證書(shū)的域名/有效期等信息单刁,
第二步是對(duì)比服務(wù)端返回的證書(shū)跟客戶(hù)端返回的是否一致灸异。
- AFSSLPinningModePublicKey:
這個(gè)模式同樣是用證書(shū)綁定方式驗(yàn)證,客戶(hù)端要有服務(wù)端的證書(shū)拷貝羔飞,
只是驗(yàn)證時(shí)只驗(yàn)證證書(shū)里的公鑰肺樟,不驗(yàn)證證書(shū)的有效期等信息。
只要公鑰是正確的逻淌,就能保證通信不會(huì)被竊聽(tīng)么伯,
因?yàn)橹虚g人沒(méi)有私鑰,無(wú)法解開(kāi)通過(guò)公鑰加密的數(shù)據(jù)卡儒。
選擇那種模式呢?
AFSSLPinningModeCertificate
最安全的比對(duì)模式田柔。但是也比較麻煩,因?yàn)樽C書(shū)是打包在APP中骨望,如果服務(wù)器證書(shū)改變或者到期硬爆,舊版本無(wú)法使用了,我們就需要用戶(hù)更新APP來(lái)使用最新的證書(shū)擎鸠。
AFSSLPinningModePublicKey
只比對(duì)證書(shū)的Public Key缀磕,只要Public Key沒(méi)有改變,證書(shū)的其他變動(dòng)都不會(huì)影響使用劣光。
如果你不能保證你的用戶(hù)總是使用你的APP的最新版本虐骑,所以我們使用AFSSLPinningModePublicKey
。
屬性
/**
服務(wù)器證書(shū)驗(yàn)證模式赎线,默認(rèn)是不驗(yàn)證
*/
@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode;
/**
驗(yàn)證服務(wù)器的證書(shū)的集合廷没,默認(rèn)情況下,AFNetworking會(huì)搜索工程中所有.cer的證書(shū)文件垂寥,但不會(huì)將某個(gè)證書(shū)作為默認(rèn)颠黎。如果想創(chuàng)建AFSecurityPolicy對(duì)象另锋,就先調(diào)用certificatesInBundle方法加載證書(shū),然后調(diào)用policyWithPinningMode:withPinnedCertificates方法創(chuàng)建對(duì)象
*/
@property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates;
/**
是否信任無(wú)效或者過(guò)期的證書(shū)狭归,默認(rèn)為否
*/
@property (nonatomic, assign) BOOL allowInvalidCertificates;
/**
是否驗(yàn)證證書(shū)中的域名夭坪,默認(rèn)為是
*/
@property (nonatomic, assign) BOOL validatesDomainName;
2、AFNetworking實(shí)現(xiàn)
創(chuàng)建自定義安全策略
// 自定義安全策略
- (AFSecurityPolicy *)customSecurityPolicy {
// 獲取證書(shū)
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"github2020" ofType:@"cer"];
NSData *certData = [NSData dataWithContentsOfFile:cerPath];
NSSet *pinnedCertificates = [[NSSet alloc] initWithObjects:certData, nil];
/*
安全模式
AFSSLPinningModeNone:完全信任服務(wù)器證書(shū)过椎;
AFSSLPinningModePublicKey:只比對(duì)服務(wù)器證書(shū)和本地證書(shū)的Public Key是否一致室梅,如果一致則信任服務(wù)器證書(shū);
AFSSLPinningModeCertificate:比對(duì)服務(wù)器證書(shū)和本地證書(shū)的所有內(nèi)容疚宇,完全一致則信任服務(wù)器證書(shū)
*/
AFSecurityPolicy *securityPolicy =
[AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey
withPinnedCertificates:pinnedCertificates];
// allowInvalidCertificates 是否允許無(wú)效證書(shū)(也就是自建的證書(shū))亡鼠,默認(rèn)為NO
// 如果是需要驗(yàn)證自建證書(shū),需要設(shè)置為YES
securityPolicy.allowInvalidCertificates = YES;
/*
validatesDomainName 是否需要驗(yàn)證域名敷待,默認(rèn)為YES间涵;
假如證書(shū)的域名與你請(qǐng)求的域名不一致,需把該項(xiàng)設(shè)置為NO榜揖;
如設(shè)成NO的話勾哩,即服務(wù)器使用其他可信任機(jī)構(gòu)頒發(fā)的證書(shū),也可以建立連接举哟,這個(gè)非常危險(xiǎn)思劳,建議打開(kāi)。
置為NO妨猩,主要用于這種情況:客戶(hù)端請(qǐng)求的是子域名敢艰,而證書(shū)上的是另外一個(gè)域名。
因?yàn)镾SL證書(shū)上的域名是獨(dú)立的册赛,假如證書(shū)上注冊(cè)的域名是www.google.com钠导,那么mail.google.com是無(wú)法驗(yàn)證通過(guò)的;
當(dāng)然森瘪,有錢(qián)可以注冊(cè)通配符的域名*.google.com牡属,但這個(gè)還是比較貴的。
如置為NO扼睬,建議自己添加對(duì)應(yīng)域名的校驗(yàn)邏輯逮栅。
*/
securityPolicy.validatesDomainName = YES;
return securityPolicy;
}
創(chuàng)建網(wǎng)絡(luò)會(huì)話管理
- (AFHTTPSessionManager *)manager {
if (!_manager) {
// 設(shè)置BaseUrl
NSURL *baseUrl = [NSURL URLWithString:@"https://github.com"];
AFHTTPSessionManager *manager =
[[AFHTTPSessionManager manager] initWithBaseURL:baseUrl];
manager.securityPolicy = [self customSecurityPolicy];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:@"text/html"];
_manager = manager;
}
return _manager;
}
發(fā)送請(qǐng)求
// 發(fā)送請(qǐng)求
- (void)sendRequest {
NSString *urlStr = @"https://github.com/AFNetworking/AFNetworking";
[self.manager GET:urlStr
parameters:nil
headers:nil
progress:nil
success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSString *str = [[NSString alloc] initWithData:responseObject
encoding:NSUTF8StringEncoding];
NSLog(@"%@",str);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"%@", error);
}];
}
附Demo鏈接:https://github.com/ZhangJingHao/ZJHSSLPinning
Charles對(duì)使用SSL Pinning前后抓包對(duì)比
參考鏈接:
如何使用SSL pinning來(lái)使你的iOS APP更加安全
證書(shū)鎖定SSL Pinning簡(jiǎn)介及用途
AFNetworking + SSL Pinning
SSL pinning using AFNetworking and NSURLSession
淺談HTTPS通信機(jī)制和Charles抓包原理