幾個(gè)概念
SSL/TSL
HTTPS就是將HTTP協(xié)議數(shù)據(jù)包放到SSL/TSL層加密后,在TCP/IP層組成IP數(shù)據(jù)報(bào)去傳輸河咽,以此保證傳輸數(shù)據(jù)的安全钠右;而對(duì)于接收端,在SSL/TSL將接收的數(shù)據(jù)包解密之后忘蟹,將數(shù)據(jù)傳給HTTP協(xié)議層飒房,就是普通的HTTP數(shù)據(jù)。 準(zhǔn)確的說SSL/TSL是會(huì)話層媚值,http是應(yīng)用層狠毯。
基本原理:
SSL/TLS協(xié)議的基本思路是采用公鑰加密法,也就是說褥芒,客戶端先向服務(wù)器端索要公鑰嚼松,然后用公鑰加密信息,服務(wù)器收到密文后锰扶,用自己的私鑰解密献酗。
(1)如何保證公鑰不被篡改?
解決方法:將公鑰放在數(shù)字證書中坷牛。只要證書是可信的罕偎,公鑰就是可信的。
(2)公鑰加密計(jì)算量太大京闰,如何減少耗用的時(shí)間颜及?
解決方法:每一次對(duì)話(session),客戶端和服務(wù)器端都生成一個(gè)"對(duì)話密鑰"(session key)蹂楣,用它來加密信息俏站。由于"對(duì)話密鑰"是對(duì)稱加密,所以運(yùn)算速度非尘杵龋快乾翔,而服務(wù)器公鑰只用于加密"對(duì)話密鑰"本身,這樣就減少了加密運(yùn)算的消耗時(shí)間。
簡(jiǎn)單的來說反浓,SSL/TSL通過四次握手萌丈,主要交換三個(gè)信息:
數(shù)字證書: 該證書包含了公鑰等信息,一般是由服務(wù)器發(fā)給客戶端雷则,接收方通過驗(yàn)證這個(gè)證書是不是由信賴的CA簽發(fā)辆雾,或者與本地的證書相對(duì)比,來判斷證書是否可信月劈;假如需要雙向驗(yàn)證度迂,則服務(wù)器和客戶端都需要發(fā)送數(shù)字證書給對(duì)方驗(yàn)證;
三個(gè)隨機(jī)數(shù):這三個(gè)隨機(jī)數(shù)構(gòu)成了后續(xù)通信過程中用來對(duì)數(shù)據(jù)進(jìn)行對(duì)稱加密解密的“對(duì)話密鑰”猜揪。首先客戶端先發(fā)第一個(gè)隨機(jī)數(shù)N1惭墓,然后服務(wù)器回了第二個(gè)隨機(jī)數(shù)N2(這個(gè)過程同時(shí)把之前提到的證書發(fā)給客戶端),這兩個(gè)隨機(jī)數(shù)都是明文的而姐;而第三個(gè)隨機(jī)數(shù)N3(這個(gè)隨機(jī)數(shù)被稱為Premaster secret)腊凶,客戶端用數(shù)字證書的公鑰進(jìn)行非對(duì)稱加密,發(fā)給服務(wù)器拴念;而服務(wù)器用只有自己知道的私鑰來解密钧萍,獲取第三個(gè)隨機(jī)數(shù)。只有政鼠,服務(wù)端和客戶端都有了三個(gè)隨機(jī)數(shù)N1+N2+N3风瘦,然后兩端就使用這三個(gè)隨機(jī)數(shù)來生成“對(duì)話密鑰”,在此之后的通信都是使用這個(gè)“對(duì)話密鑰”來進(jìn)行對(duì)稱加密解密公般。因?yàn)檫@個(gè)過程中万搔,服務(wù)端的私鑰只用來解密第三個(gè)隨機(jī)數(shù),從來沒有在網(wǎng)絡(luò)中傳輸過官帘,這樣的話蟹略,只要私鑰沒有被泄露,那么數(shù)據(jù)就是安全的遏佣。
加密通信協(xié)議:就是雙方商量使用哪一種加密方式挖炬,假如兩者支持的加密方式不匹配,則無法進(jìn)行通信状婶;
有個(gè)常見的問題意敛,關(guān)于隨機(jī)數(shù)為什么要三個(gè)?只最后一個(gè)隨機(jī)數(shù)N3不可以么膛虫?
這是由于SSL/TLS設(shè)計(jì)草姻,就假設(shè)服務(wù)器不相信所有的客戶端都能夠提供完全隨機(jī)數(shù),假如某個(gè)客戶端提供的隨機(jī)數(shù)不隨機(jī)的話稍刀,就大大增加了“對(duì)話密鑰”被破解的風(fēng)險(xiǎn)撩独,所以由三組隨機(jī)數(shù)組成最后的隨機(jī)數(shù)敞曹,保證了隨機(jī)數(shù)的隨機(jī)性,以此來保證每次生成的“對(duì)話密鑰”安全性综膀。
圖解SSL/TLS協(xié)議
SSL/TLS協(xié)議運(yùn)行機(jī)制的概述
數(shù)字證書
什么是CA證書澳迫?
CA 證書,顧名思義剧劝,就是CA頒發(fā)的證書橄登。
前面已經(jīng)說了,人人都可以找工具制作證書讥此。但是你一個(gè)小破孩制作出來的證書是沒啥用處的拢锹。因?yàn)槟悴皇菣?quán)威的CA機(jī)關(guān),你自己搞的證書不具有權(quán)威性萄喳。
這就好比上述的例子里卒稳,某個(gè)壞人自己刻了一個(gè)公章,蓋到介紹信上他巨。但是別人一看展哭,不是受信任的中介公司的公章,就不予理睬闻蛀。壞蛋的陰謀就不能得逞啦。
什么是證書信任鏈您市?
實(shí)際上觉痛,證書之間的信任關(guān)系,是可以嵌套的茵休。比如薪棒,C 信任 A1,A1 信任 A2榕莺,A2 信任 A3......這個(gè)叫做證書的信任鏈俐芯。只要你信任鏈上的頭一個(gè)證書,那后續(xù)的證書钉鸯,都是可以信任滴吧史。
什么是根證書?
“根證書”的洋文叫“root certificate”唠雕,專業(yè)的解釋看“這里”贸营。為了說清楚根證書是咋回事,再來看個(gè)稍微復(fù)雜點(diǎn)的例子岩睁。
假設(shè) C 證書信任 A 和 B钞脂;然后 A 信任 A1 和 A2;B 信任 B1 和 B2捕儒。則它們之間冰啃,構(gòu)成如下的一個(gè)樹形關(guān)系(一個(gè)倒立的樹)。
處于最頂上的樹根位置的那個(gè)證書,就是“根證書”阎毅。除了根證書焚刚,其它證書都要依靠上一級(jí)的證書,來證明自己净薛。那誰來證明“根證書”可靠捏汪榔?實(shí)際上,根證書自己證明自己是可靠滴(或者換句話說肃拜,根證書是不需要被證明滴)痴腌。
聰明的同學(xué)此刻應(yīng)該意識(shí)到了:根證書是整個(gè)證書體系安全的根本。所以燃领,如果某個(gè)證書體系中士聪,根證書出了問題(不再可信了),那么所有被根證書所信任的其它證書猛蔽,也就不再可信了剥悟。這個(gè)后果是相當(dāng)相當(dāng)?shù)螄?yán)重(簡(jiǎn)直可以說是災(zāi)難性的)。
這也是中間人攻擊的一個(gè)入口曼库。所以如果不小心安裝過非權(quán)威機(jī)構(gòu)的根證書区岗,比如客戶端鏈接惡意的 Wi-Fi等情況下裝上惡意證書,這時(shí)候設(shè)備上就多了一個(gè)預(yù)設(shè)的公鑰毁枯,那么用惡意私鑰加密的證書就能被正常解析出來慈缔。所以千萬不要隨便裝根證書,這等于是為那些惡意證書留了一扇門种玛。
iOS的Https安全校驗(yàn)
現(xiàn)在為加強(qiáng)ios App的安全藐鹤,基本所有的app都采用https訪問接口和H5頁(yè)面。
低安全NSURLSession默認(rèn)校驗(yàn)
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler {
NSURLProtectionSpace *protectionSpace = challenge.protectionSpace;
SecTrustRef serverTrust = protectionSpace.serverTrust;
__block NSURLCredential *credential = nil;
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if (BFServerTrustIsValid(serverTrust)) {
disposition = NSURLSessionAuthChallengeUseCredential;
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
} else {
disposition = NSURLSessionAuthChallengeRejectProtectionSpace;
}
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
if (completionHandler) {
completionHandler(disposition, credential);
}
}
static BOOL BFServerTrustIsValid(SecTrustRef serverTrust) {
BOOL isValid = NO;
SecTrustResultType result;
__Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out);
isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
_out:
return isValid;
}
中級(jí)安全CA證書默認(rèn)校驗(yàn)+域名host白名單
我們可以建立一個(gè)host white list赂韵, 在得到對(duì)象NSURLProtectionSpace的時(shí)候娱节,首先校驗(yàn)host。
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler {
NSURLProtectionSpace *protectionSpace = challenge.protectionSpace;
SecTrustRef serverTrust = protectionSpace.serverTrust;
__block NSURLCredential *credential = nil;
// 這里到host白名單中校驗(yàn)是否安全
BOOL trust = [[AppStatus sharedManager] matchHost:challenge.protectionSpace.host isWebView:NO];
if (!trust) {
if (completionHandler) {
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}
return;
}
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if (BFServerTrustIsValid(serverTrust)) {
disposition = NSURLSessionAuthChallengeUseCredential;
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
} else {
disposition = NSURLSessionAuthChallengeRejectProtectionSpace;
}
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
if (completionHandler) {
completionHandler(disposition, credential);
}
}
高安全使用自簽名證書
這種方式就是在app中存放一份服務(wù)端的證書祭示。然后客戶端證書和服務(wù)器證書進(jìn)行強(qiáng)校驗(yàn)肄满。
這里也有幾種方式:
- 只校驗(yàn)證書里的公鑰,不校驗(yàn)域名和有效期等质涛,只要公鑰是正確的悄窃,就能保證通信不會(huì)被竊聽,因?yàn)橹虚g人沒有私鑰蹂窖,無法解開通過公鑰加密的數(shù)據(jù)轧抗。
- 設(shè)置本地為錨點(diǎn)證書,假如驗(yàn)證的數(shù)字證書是這個(gè)錨點(diǎn)證書的子節(jié)點(diǎn)瞬测,即驗(yàn)證的數(shù)字證書是由錨點(diǎn)證書對(duì)應(yīng)CA或子CA簽發(fā)的横媚,或是該證書本身纠炮,則信任該證書)。從服務(wù)器端證書鏈的根節(jié)點(diǎn)往下遍歷灯蝴,看看是否有與客戶端的綁定證書一致的恢口,有的話,就說明服務(wù)器端是可信的穷躁。
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain{
self.allowInvalidCertificates==YES表示如果此處允許使用自建證書(服務(wù)器自己弄的CA證書耕肩,非官方),并且還想驗(yàn)證domain是否有效(self.validatesDomainName == YES)问潭,也就是說你想驗(yàn)證自建證書的domain是否有效猿诸。那么你必須使用pinnedCertificates(就是在客戶端保存服務(wù)器端頒發(fā)的證書拷貝)才可以。但是你的SSLPinningMode為AFSSLPinningModeNone狡忙,表示你不使用SSL pinning梳虽,只跟瀏覽器一樣在系統(tǒng)的信任機(jī)構(gòu)列表里驗(yàn)證服務(wù)端返回的證書。所以當(dāng)然你的客戶端上沒有你導(dǎo)入的pinnedCertificates灾茁,同樣表示你無法驗(yàn)證該自建證書窜觉。所以都返回NO。最終結(jié)論就是要使用服務(wù)器端自建證書北专,那么就得將對(duì)應(yīng)的證書拷貝到iOS客戶端禀挫,并使用AFSSLPinningMode或AFSSLPinningModePublicKey
if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
// https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/OverridingSSLChainValidationCorrectly.html
NSLog(@"In order to validate a domain name for self signed certificates, you MUST use pinning.");
return NO;
}
// 此處設(shè)置驗(yàn)證證書的策略
NSMutableArray *policies = [NSMutableArray array];
if (self.validatesDomainName) {
// 如果需要驗(yàn)證domain,那么就使用SecPolicyCreateSSL函數(shù)創(chuàng)建驗(yàn)證策略拓颓,其中第一個(gè)參數(shù)為true表示驗(yàn)證整個(gè)SSL證書鏈语婴,第二個(gè)參數(shù)傳入domain,用于判斷整個(gè)證書鏈上葉子節(jié)點(diǎn)表示的那個(gè)domain是否和此處傳入domain一致
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
// 如果不需要驗(yàn)證domain录粱,就使用默認(rèn)的BasicX509驗(yàn)證策略
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}
// 為serverTrust設(shè)置驗(yàn)證策略,即告訴客戶端如何驗(yàn)證serverTrust
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
// 如果SSLPinningMode為 AFSSLPinningModeNone画拾,表示你不使用SSL pinning啥繁,但是我允許自建證書,那么返回YES青抛,或者使用AFServerTrustIsValid函數(shù)看看serverTrust是否可信任旗闽,如果信任,也返回YES
if (self.SSLPinningMode == AFSSLPinningModeNone) {
return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
} else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
// 既不允許自建證書蜜另,而且使用AFServerTrustIsValid函數(shù)又返回NO适室,那么該serverTrust就真的不能通過驗(yàn)證了
return NO;
}
switch (self.SSLPinningMode) {
// 理論上,上面那個(gè)部分已經(jīng)解決了self.SSLPinningMode)為AFSSLPinningModeNone)等情況举瑰,所以此處再遇到捣辆,就直接返回NO
case AFSSLPinningModeNone:
default:
return NO;
// 這個(gè)模式表示用證書綁定(SSL Pinning)方式驗(yàn)證證書,需要客戶端保存有服務(wù)端的證書拷貝
// 注意客戶端保存的證書存放在self.pinnedCertificates中
case AFSSLPinningModeCertificate: {
NSMutableArray *pinnedCertificates = [NSMutableArray array];
for (NSData *certificateData in self.pinnedCertificates) {
// 這里使用SecCertificateCreateWithData函數(shù)對(duì)原先的pinnedCertificates做一些處理此迅,保證返回的證書都是DER編碼的X.509證書
[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
}
// 將pinnedCertificates設(shè)置成需要參與驗(yàn)證的Anchor Certificate(錨點(diǎn)證書汽畴,通過SecTrustSetAnchorCertificates設(shè)置了參與校驗(yàn)錨點(diǎn)證書之后旧巾,假如驗(yàn)證的數(shù)字證書是這個(gè)錨點(diǎn)證書的子節(jié)點(diǎn),即驗(yàn)證的數(shù)字證書是由錨點(diǎn)證書對(duì)應(yīng)CA或子CA簽發(fā)的忍些,或是該證書本身鲁猩,則信任該證書),具體就是調(diào)用SecTrustEvaluate來驗(yàn)證罢坝。
SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);
if (!AFServerTrustIsValid(serverTrust)) {
return NO;
}
// 服務(wù)器端的證書鏈廓握,注意此處返回的證書鏈順序是從葉節(jié)點(diǎn)到根節(jié)點(diǎn)
NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
// 從服務(wù)器端證書鏈的根節(jié)點(diǎn)往下遍歷,看看是否有與客戶端的綁定證書一致的嘁酿,有的話隙券,就說明服務(wù)器端是可信的。因?yàn)楸闅v順序正好相反痹仙,所以使用reverseObjectEnumerator
for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]){
if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
return YES;
}
}
return NO;
}
// AFSSLPinningModePublicKey模式同樣是用證書綁定(SSL Pinning)方式驗(yàn)證是尔,客戶端要有服務(wù)端的證書拷貝,只是驗(yàn)證時(shí)只驗(yàn)證證書里的公鑰开仰,不驗(yàn)證證書的有效期等信息拟枚。只要公鑰是正確的,就能保證通信不會(huì)被竊聽众弓,因?yàn)橹虚g人沒有私鑰恩溅,無法解開通過公鑰加密的數(shù)據(jù)。
case AFSSLPinningModePublicKey: {
NSUInteger trustedPublicKeyCount = 0;
// 從serverTrust中取出服務(wù)器端傳過來的所有可用的證書谓娃,并依次得到相應(yīng)的公鑰
NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);
// 依次遍歷這些公鑰脚乡,如果和客戶端綁定證書的公鑰一致,那么就給trustedPublicKeyCount加一
for (id trustChainPublicKey in publicKeys) {
for (id pinnedPublicKey in self.pinnedPublicKeys) {
if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
trustedPublicKeyCount += 1;
}
}
}
// trustedPublicKeyCount大于0說明服務(wù)器端中的某個(gè)證書和客戶端綁定的證書公鑰一致滨达,認(rèn)為服務(wù)器端是可信的
return trustedPublicKeyCount > 0;
}
}
return NO;
}
AFN提供了各種校驗(yàn)證書的方式奶稠。可以參數(shù)源碼
AFSecurityPolicy - 網(wǎng)絡(luò)安全策略
總結(jié)
不安全的 HTTP 協(xié)議捡遍,很容易被網(wǎng)絡(luò)嗅探锌订,中間人劫持和篡改,從而泄露隱私信息画株,冒充偽造虛 假的請(qǐng)求等辆飘。雖然 HTTPS 可以從理論上很好的解決竊聽、冒充谓传、篡改等風(fēng)險(xiǎn)蜈项,但是由于客戶端 和服務(wù)器實(shí)現(xiàn)和配置不當(dāng),很可能導(dǎo)致 HTTPS 被繞過续挟〗糇洌客戶端證書校驗(yàn)不全面或有誤就是比較 典型的場(chǎng)景,通常表現(xiàn)為未對(duì)證書域名做校驗(yàn)诗祸,忽略證書校驗(yàn)錯(cuò)誤等常侦。
建議采用 HTTPS浇冰,對(duì)證書域名做校驗(yàn),不要忽略證書校驗(yàn)錯(cuò)誤聋亡,也可以采用固定證書的形式肘习,但 要注意固定證書的更新機(jī)制。
參考文獻(xiàn)
數(shù)字證書及CA的掃盲介紹
iOS關(guān)于HTTPS支持并防止中間人攻擊
打造安全的App坡倔!iOS安全系列之 HTTPS
iOS對(duì)HTTPS證書鏈的驗(yàn)證
九個(gè)問題從入門到熟悉HTTPS