本文主要記錄關(guān)于 AFSecurityPolicy 模塊的相關(guān)內(nèi)容,主要是AFSecurityPolicy.m
, 是 HTTPS網(wǎng)絡(luò)請(qǐng)求中的安全模塊.
HTTPS
HTTPs 的基本內(nèi)容可以參考維基百科
其中的身份認(rèn)證的方法是基于公開密鑰加密算法的身份驗(yàn)證是指通信中的雙方分別持有公開密鑰和私有密鑰,由其中的一方采用私有密鑰對(duì)特定數(shù)據(jù)進(jìn)行加密源譬,而對(duì)方采用公開密鑰對(duì)數(shù)據(jù)進(jìn)行解密帖世,如果解密成功缓醋,就認(rèn)為用戶是合法用戶,否則就認(rèn)為是身份驗(yàn)證失敗呕寝。使用基于公開密鑰加密算法的身份驗(yàn)證的服務(wù)有:SSL, 數(shù)字簽名等等。
bangs blog 的解釋以及有兩個(gè)重要的問題如下:
HTTPS連接建立過程大致是,客戶端和服務(wù)端建立一個(gè)連接灿渴,服務(wù)端返回一個(gè)證書,客戶端里存有各個(gè)受信任的證書機(jī)構(gòu)根證書胰舆,用這些根證書對(duì)服務(wù)端返回的證書進(jìn)行驗(yàn)證骚露,經(jīng)驗(yàn)證如果證書是可信任的,就生成一個(gè)pre-master secret缚窿,用這個(gè)證書的公鑰加密后發(fā)送給服務(wù)端棘幸,服務(wù)端用私鑰解密后得到pre-master secret,再根據(jù)某種算法生成master secret倦零,客戶端也同樣根據(jù)這種算法從pre-master secret生成master secret误续,隨后雙方的通信都用這個(gè)master secret對(duì)傳輸數(shù)據(jù)進(jìn)行加密解密吨悍。
以上是簡(jiǎn)單過程,中間還有很多細(xì)節(jié)蹋嵌,詳細(xì)過程和原理已經(jīng)有很多文章闡述得很好育瓜,就不再?gòu)?fù)述,推薦一些相關(guān)文章:
關(guān)于非對(duì)稱加密算法的原理:RSA算法原理<一> <二>
關(guān)于整個(gè)流程:HTTPS那些事<一> <二> <三>
關(guān)于數(shù)字證書:淺析數(shù)字證書
1.證書是怎樣驗(yàn)證的栽烂?怎樣保證中間人不能偽造證書躏仇?
首先要知道非對(duì)稱加密算法的特點(diǎn),非對(duì)稱加密有一對(duì)公鑰私鑰腺办,用公鑰加密的數(shù)據(jù)只能通過對(duì)應(yīng)的私鑰解密焰手,用私鑰加密的數(shù)據(jù)只能通過對(duì)應(yīng)的公鑰解密。
我們來看最簡(jiǎn)單的情況:一個(gè)證書頒發(fā)機(jī)構(gòu)(CA)怀喉,頒發(fā)了一個(gè)證書A书妻,服務(wù)器用這個(gè)證書建立https連接』撬停客戶端在信任列表里有這個(gè)CA機(jī)構(gòu)的根證書驻子。
首先CA機(jī)構(gòu)頒發(fā)的證書A里包含有證書內(nèi)容F,以及證書加密內(nèi)容F1估灿,加密內(nèi)容F1就是用這個(gè)證書機(jī)構(gòu)的私鑰對(duì)內(nèi)容F加密的結(jié)果崇呵。(這中間還有一次hash算法,略過馅袁。)
建立https連接時(shí)域慷,服務(wù)端返回證書A給客戶端,客戶端的系統(tǒng)里的CA機(jī)構(gòu)根證書有這個(gè)CA機(jī)構(gòu)的公鑰汗销,用這個(gè)公鑰對(duì)證書A的加密內(nèi)容F1解密得到F2犹褒,跟證書A里內(nèi)容F對(duì)比,若相等就通過驗(yàn)證弛针。整個(gè)流程大致是:F->CA私鑰加密->F1->客戶端CA公鑰解密->F叠骑。因?yàn)橹虚g人不會(huì)有CA機(jī)構(gòu)的私鑰,客戶端無法通過CA公鑰解密削茁,所以偽造的證書肯定無法通過驗(yàn)證宙枷。
2.什么是SSL Pinning?
可以理解為證書綁定茧跋,是指客戶端直接保存服務(wù)端的證書慰丛,建立https連接時(shí)直接對(duì)比服務(wù)端返回的和客戶端保存的兩個(gè)證書是否一樣,一樣就表明證書是真的瘾杭,不再去系統(tǒng)的信任證書機(jī)構(gòu)里尋找驗(yàn)證诅病。這適用于非瀏覽器應(yīng)用,因?yàn)闉g覽器跟很多未知服務(wù)端打交道,無法把每個(gè)服務(wù)端的證書都保存到本地贤笆,但CS架構(gòu)的像手機(jī)APP事先已經(jīng)知道要進(jìn)行通信的服務(wù)端蝇棉,可以直接在客戶端保存這個(gè)服務(wù)端的證書用于校驗(yàn)。
為什么直接對(duì)比就能保證證書沒問題苏潜?如果中間人從客戶端取出證書银萍,再偽裝成服務(wù)端跟其他客戶端通信,它發(fā)送給客戶端的這個(gè)證書不就能通過驗(yàn)證嗎恤左?確實(shí)可以通過驗(yàn)證,但后續(xù)的流程走不下去搀绣,因?yàn)橄乱徊娇蛻舳藭?huì)用證書里的公鑰加密飞袋,中間人沒有這個(gè)證書的私鑰就解不出內(nèi)容,也就截獲不到數(shù)據(jù)链患,這個(gè)證書的私鑰只有真正的服務(wù)端有巧鸭,中間人偽造證書主要偽造的是公鑰。
為什么要用SSL Pinning麻捻?正常的驗(yàn)證方式不夠嗎纲仍?如果服務(wù)端的證書是從受信任的的CA機(jī)構(gòu)頒發(fā)的,驗(yàn)證是沒問題的贸毕,但CA機(jī)構(gòu)頒發(fā)證書比較昂貴郑叠,小企業(yè)或個(gè)人用戶可能會(huì)選擇自己頒發(fā)證書,這樣就無法通過系統(tǒng)受信任的CA機(jī)構(gòu)列表驗(yàn)證這個(gè)證書的真?zhèn)瘟嗣鞴鳎孕枰猄SL Pinning這樣的方式去驗(yàn)證乡革。
關(guān)于Certificate Pinning
在SSL/TLS通信中,客戶端通過數(shù)字證書判斷服務(wù)器是否可信摊腋,并采用證書的公鑰與服務(wù)器進(jìn)行加密通信.
然而在很多移動(dòng)應(yīng)用中,開發(fā)者不檢查服務(wù)器證書的有效性沸版,或選擇接受所有的證書,這樣就會(huì)導(dǎo)致中間人攻擊.
事實(shí)上,app大多只和固定的服務(wù)器通信,因此可以在代碼更精確地直接驗(yàn)證某張?zhí)囟ǖ淖C書兴蒸,這種方法稱為“證書鎖定”(certificate pinning)
如果進(jìn)行 Certificate Pinning
具體步驟如下:
1 首先通過服務(wù)器端使用RSA算法生成一對(duì)公鑰私鑰對(duì)视粮,服務(wù)器端持有私鑰,線下將公鑰傳給客戶端橙凳。App中將這個(gè)值硬編碼到本地蕾殴。
2 App端可以自己實(shí)現(xiàn)一個(gè)X509TrustManager接口,在其中的CheckServerTrusted()方法里通過證書鏈拿到PublicKey
3 比較1和2中進(jìn)行md5的值,如果匹配則服務(wù)器驗(yàn)證通過痕惋,否則立即終止與此服務(wù)器的通信.
AFNetworking 就是采用的這種方法.
如果引入第三方的支付等
在實(shí)際應(yīng)用中区宇,一款移動(dòng)應(yīng)用往往不止一個(gè)后臺(tái),尤其是在支付類產(chǎn)品中值戳,經(jīng)常需要去集成第三方支付網(wǎng)關(guān)议谷。
但是第三方的服務(wù)什么時(shí)候會(huì)更改證書,這個(gè)就說不準(zhǔn)了堕虹。初期卧晓,我們會(huì)把所有的這些第三方的服務(wù)提供的PublicKey都硬編碼在本地芬首,但是有次其中一個(gè)服務(wù)商自己改掉了,造成用戶手上的產(chǎn)品直接不能使用了逼裆, 這個(gè)就給我們帶來了不必要的麻煩郁稍。
具體的解決方案如下:
1 自己的服務(wù)端使用RSA算法生成一對(duì)公鑰私鑰對(duì),服務(wù)器端持有私鑰胜宇,線下將公鑰傳給客戶端耀怜。App中將這個(gè)值硬編碼到本地
2 自己的服務(wù)端提供API,獲得當(dāng)前所有服務(wù)器(包括第三方)公鑰的SHASUM值.當(dāng)然這個(gè)值必須通過1中存在本地PublicKey簽名驗(yàn)證得到
3 再通過最基本Certificate Pinning的辦法(上文提到),直接從各服務(wù)端拿到各自公鑰桐愉, 連同本地PublicKey一起計(jì)算SHASUM
4 比較2和3中的值财破,如果相同則Certificate Pinning通過,否則終止app
詳細(xì)的參考資料:
https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning
iOS 中實(shí)現(xiàn)對(duì) HTTPS 的支持
詳細(xì)參考資料: http://oncenote.com/2014/10/21/Security-1-HTTPS/
首先从诲,需要明確你使用HTTP/HTTPS的用途左痢,因?yàn)镺SX和iOS平臺(tái)提供了多種API,來支持不同的用途系洛,官方文檔《Making HTTP and HTTPS Requests》有詳細(xì)的說明俊性,而文檔《HTTPS Server Trust Evaluation》則詳細(xì)講解了HTTPS驗(yàn)證相關(guān)知識(shí),這里就不多說了描扯。主要講解我們最常用的NSURLConnection支持HTTPS的實(shí)現(xiàn)(NSURLSession的實(shí)現(xiàn)方法類似定页,只是要求授權(quán)證明的回調(diào)不一樣而已),以及怎么樣使用AFNetworking這個(gè)非常流行的第三方庫(kù)來支持HTTPS荆烈。
驗(yàn)證證書的API
相關(guān)的Api在Security Framework中拯勉,驗(yàn)證流程如下:
- 第一步,先獲取需要驗(yàn)證的信任對(duì)象(Trust Object)憔购。這個(gè)Trust Object在不同的應(yīng)用場(chǎng)景下獲取的方式都不一樣宫峦,對(duì)于NSURLConnection來說,是從delegate方法-connection:willSendRequestForAuthenticationChallenge:
回調(diào)回來的參數(shù)challenge中獲取([challenge.protectionSpace serverTrust]
)玫鸟。
- 使用系統(tǒng)默認(rèn)驗(yàn)證方式驗(yàn)證Trust Object导绷。SecTrustEvaluate
會(huì)根據(jù)Trust Object的驗(yàn)證策略,一級(jí)一級(jí)往上屎飘,驗(yàn)證證書鏈上每一級(jí)數(shù)字簽名的有效性(上一部分有講解)妥曲,從而評(píng)估證書的有效性。 - 如第二步驗(yàn)證通過了钦购,一般的安全要求下檐盟,就可以直接驗(yàn)證通過,進(jìn)入到下一步:使用Trust Object生成一份憑證([NSURLCredential credentialForTrust:serverTrust]
)押桃,傳入challenge的sender中([challenge.sender useCredential:cred forAuthenticationChallenge:challenge]
)處理葵萎,建立連接。 - 假如有更強(qiáng)的安全要求,可以繼續(xù)對(duì)Trust Object進(jìn)行更嚴(yán)格的驗(yàn)證羡忘。常用的方式是在本地導(dǎo)入證書谎痢,驗(yàn)證Trust Object與導(dǎo)入的證書是否匹配。更多的方法可以查看Enforcing Stricter Server Trust Evaluation卷雕,這一部分在講解AFNetworking源碼中會(huì)講解到节猿。
- 假如驗(yàn)證失敗,取消此次Challenge-Response Authentication驗(yàn)證流程漫雕,拒絕連接請(qǐng)求滨嘱。
ps: 假如是自建證書的,則不使用第二步系統(tǒng)默認(rèn)的驗(yàn)證方式浸间,因?yàn)樽越ㄗC書的根CA的數(shù)字簽名未在操作系統(tǒng)的信任列表中九孩。
iOS授權(quán)驗(yàn)證的API和流程大概了解了,下面发框,我們看看在NSURLConnection中的代碼實(shí)現(xiàn):
使用NSURLConnection支持HTTPS的實(shí)現(xiàn)
// Now start the connection
NSURL * httpsURL = [NSURL URLWithString:@"https://www.google.com"];
self.connection = [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:httpsURL] delegate:self];
//回調(diào)
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
//1)獲取trust object
SecTrustRef trust = challenge.protectionSpace.serverTrust;
SecTrustResultType result;
//2)SecTrustEvaluate對(duì)trust進(jìn)行驗(yàn)證
OSStatus status = SecTrustEvaluate(trust, &result);
if (status == errSecSuccess &&
(result == kSecTrustResultProceed || result == kSecTrustResultUnspecified)) {
//3)驗(yàn)證成功,生成NSURLCredential憑證cred煤墙,告知challenge的sender使用這個(gè)憑證來繼續(xù)連接
NSURLCredential *cred = [NSURLCredential credentialForTrust:trust];
[challenge.sender useCredential:cred forAuthenticationChallenge:challenge];
} else {
//5)驗(yàn)證失敗梅惯,取消這次驗(yàn)證流程
[challenge.sender cancelAuthenticationChallenge:challenge];
}
}
上面是代碼是通過系統(tǒng)默認(rèn)驗(yàn)證流程來驗(yàn)證證書的。假如我們是自建證書的呢仿野?這樣Trust Object里面服務(wù)器的證書因?yàn)椴皇强尚湃蔚腃A簽發(fā)的铣减,所以直接使用SecTrustEvaluate
進(jìn)行驗(yàn)證是不會(huì)成功。又或者脚作,即使服務(wù)器返回的證書是信任CA簽發(fā)的葫哗,又如何確定這證書就是我們想要的特定證書?這就需要先在本地導(dǎo)入證書球涛,設(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)證襟己。代碼如下
//先導(dǎo)入證書
NSString * cerPath = ...; //證書的路徑
NSData * cerData = [NSData dataWithContentsOfFile:cerPath];
SecCertificateRef certificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)(cerData));
self.trustedCertificates = @[CFBridgingRelease(certificate)];
//回調(diào)
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
//1)獲取trust object
SecTrustRef trust = challenge.protectionSpace.serverTrust;
SecTrustResultType result;
//注意:這里將之前導(dǎo)入的證書設(shè)置成下面驗(yàn)證的Trust Object的anchor certificate
SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)self.trustedCertificates);
//2)SecTrustEvaluate會(huì)查找前面SecTrustSetAnchorCertificates設(shè)置的證書或者系統(tǒng)默認(rèn)提供的證書,對(duì)trust進(jìn)行驗(yàn)證
OSStatus status = SecTrustEvaluate(trust, &result);
if (status == errSecSuccess &&
(result == kSecTrustResultProceed ||
result == kSecTrustResultUnspecified)) {
//3)驗(yàn)證成功牍陌,生成NSURLCredential憑證cred擎浴,告知challenge的sender使用這個(gè)憑證來繼續(xù)連接
NSURLCredential *cred = [NSURLCredential credentialForTrust:trust];
[challenge.sender useCredential:cred forAuthenticationChallenge:challenge];
} else {
//5)驗(yàn)證失敗,取消這次驗(yàn)證流程
[challenge.sender cancelAuthenticationChallenge:challenge];
}
}
建議采用本地導(dǎo)入證書的方式驗(yàn)證證書毒涧,來保證足夠的安全性贮预。更多的驗(yàn)證方法,請(qǐng)查看官方文檔《HTTPS Server Trust Evaluation》
使用 AFNetworking 的安全設(shè)置
Pinning Mode
AFNetworking 的安全相關(guān)的設(shè)置在AFSecurityPolicy
,它定義3種 SSL Pinning Mode:
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
AFSSLPinningModeNone,
AFSSLPinningModePublicKey,
AFSSLPinningModeCertificate,
};
- AFSSLPinningModeNone : 你不必將憑證跟你的 APP 一起打包,完全信任服務(wù)器的憑證
- AFSSLPinningModeCertificate : 對(duì)比服務(wù)器憑證跟你的憑證是否完全匹配
- AFSSLPinningModePublicKey : 只對(duì)比服務(wù)器憑證的 publick key跟你的憑證的 public key 是否匹配
那么使用哪種方式比較好?
AFSSLPinningModeCertificate
比較安全同時(shí)比較麻煩, 它會(huì)對(duì)比你打包的憑證和服務(wù)器的憑證是否一致.因?yàn)槟愕膽{證是和 app 一起打包的,這也就代表說如果你的憑證過期或者變化了,你就必須得更新 app,而且舊版 app 無法使用了.當(dāng)然也可以在每次 app 啟動(dòng)時(shí)候就去某個(gè)服務(wù)器下載最新的憑證,不過此時(shí)下載鏈接有風(fēng)險(xiǎn).
AFSSLPinningModePublicKey
則是只有對(duì)比憑證里面的 public key, 所以即使服務(wù)器的憑證有所變動(dòng),只要 public key 沒變化, 就能通過驗(yàn)證
一般情況下使用AFSSLPinningModePublicKey
,除非你能保證 app 使用者都只用最新版本的 app.
Certification Chain
/** Whether to evaluate an entire SSL certificate chain, or just the leaf certificate. Defaults to `YES`. */
@property (nonatomic, assign) BOOL validatesCertificateChain;
如果你的憑證是某個(gè)機(jī)構(gòu)發(fā)出的,該機(jī)構(gòu)的憑證是由另外一家更加高級(jí)的機(jī)構(gòu)發(fā)出的,一路往上追,這樣一串由各個(gè)機(jī)構(gòu)發(fā)出的憑證稱為 certification chain.
如果你把這個(gè)屬性設(shè)置為 Yes,那就得把這一串憑證全部打包到你的 APP,必須每個(gè)驗(yàn)證都通過才算通過,如果設(shè)置為 No, 只需要打包你自己的憑證就夠了.
在2.6x 以后的版本的 AFNetworking 以后已經(jīng)沒有
validatesCertificateChain
這個(gè)屬性.
參考連接:
http://robnapier.net/pinning-your-ssl-certs
http://stackoverflow.com/questions/24615144/afnetworking-pin-public-key-for-a-trusted-certificate/24625969#24625969
http://oncenote.com/2014/10/21/Security-1-HTTPS/
https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning
AFNetworking 中 HTTPS 實(shí)例
NSURL * url = [NSURL URLWithString:@"https://www.google.com"];
AFHTTPRequestOperationManager * requestOperationManager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:url];
dispatch_queue_t requestQueue = dispatch_create_serial_queue_for_name("kRequestCompletionQueue");
requestOperationManager.completionQueue = requestQueue;
AFSecurityPolicy * securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
//allowInvalidCertificates 是否允許無效證書(也就是自建的證書)萌狂,默認(rèn)為NO
//如果是需要驗(yàn)證自建證書档玻,需要設(shè)置為YES
securityPolicy.allowInvalidCertificates = YES;
//validatesDomainName 是否需要驗(yàn)證域名,默認(rèn)為YES茫藏;
//假如證書的域名與你請(qǐng)求的域名不一致误趴,需把該項(xiàng)設(shè)置為NO;如設(shè)成NO的話务傲,即服務(wù)器使用其他可信任機(jī)構(gòu)頒發(fā)的證書凉当,也可以建立連接,這個(gè)非常危險(xiǎn)售葡,建議打開缚态。
//置為NO误证,主要用于這種情況:客戶端請(qǐng)求的是子域名,而證書上的是另外一個(gè)域名。因?yàn)镾SL證書上的域名是獨(dú)立的叶骨,假如證書上注冊(cè)的域名是www.google.com,那么mail.google.com是無法驗(yàn)證通過的恢共;當(dāng)然晃听,有錢可以注冊(cè)通配符的域名*.google.com,但這個(gè)還是比較貴的介却。
//如置為NO谴供,建議自己添加對(duì)應(yīng)域名的校驗(yàn)邏輯。
securityPolicy.validatesDomainName = YES;
//validatesCertificateChain 是否驗(yàn)證整個(gè)證書鏈齿坷,默認(rèn)為YES
//設(shè)置為YES桂肌,會(huì)將服務(wù)器返回的Trust Object上的證書鏈與本地導(dǎo)入的證書進(jìn)行對(duì)比,這就意味著永淌,假如你的證書鏈?zhǔn)沁@樣的:
//GeoTrust Global CA
// Google Internet Authority G2
// *.google.com
//那么崎场,除了導(dǎo)入*.google.com之外,還需要導(dǎo)入證書鏈上所有的CA證書(GeoTrust Global CA, Google Internet Authority G2)仰禀;
//如是自建證書的時(shí)候照雁,可以設(shè)置為YES,增強(qiáng)安全性答恶;假如是信任的CA所簽發(fā)的證書饺蚊,則建議關(guān)閉該驗(yàn)證,因?yàn)檎麄€(gè)證書鏈一一比對(duì)是完全沒有必要(請(qǐng)查看源代碼)悬嗓;
securityPolicy.validatesCertificateChain = NO;
requestOperationManager.securityPolicy = securityPolicy;
AFSecurity 在源碼中的位置
AFHTTPRequestOperationManager ,AFURLConnectionOperation, 類中有以下屬性
The security policy used by created request operations to evaluate server trust for secure connections. `AFHTTPRequestOperationManager` uses the `defaultPolicy` unless otherwise specified.
*/
@property (nonatomic, strong) AFSecurityPolicy *securityPolicy;
最后這個(gè)屬性被設(shè)置成AFURLConnectionOperation的以上屬性, 通過該屬性的注釋 -- 用于 request 中驗(yàn)證服務(wù)器身份.
具體使用代碼:
//把服務(wù)端證書(需要轉(zhuǎn)換成cer格式)放到APP項(xiàng)目資源里污呼,AFSecurityPolicy會(huì)自動(dòng)尋找根目錄下所有cer文件,當(dāng)然也可以自己打包到自己的 xxx.bundle 中,但是需要在源碼中進(jìn)行一定的設(shè)置
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode: AFSSLPinningModePublicKey];
securityPolicy.allowInvalidCertificates = YES;
[AFHTTPRequestOperationManager manager].securityPolicy = securityPolicy;
[manager GET:@"[https://example.com/](https://example.com/)"
parameters:nil
success:^(AFHTTPRequestOperation *operation, id, responseObject) {
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
}];
具體的源碼注釋可以參考 bangs blog
個(gè)人準(zhǔn)備總結(jié)一份關(guān)于 iOS 可能用到的 HTTPS 相關(guān)的系列文章