一、引言
本篇博客主要討論如何在客戶端與服務(wù)端之間進(jìn)行HTTPS網(wǎng)絡(luò)傳輸,為了深入理解網(wǎng)絡(luò)傳輸?shù)幕A(chǔ)原理,更加靈活的校驗(yàn)證書,博客的前半部分也將介紹一些HTTPS網(wǎng)絡(luò)傳輸原理。當(dāng)然,文章中有不正和疏漏之處,還望朋友不吝指正,感謝!
二、HTTP與HTTPS
我們都知道,HTTP是一種常用的網(wǎng)絡(luò)傳輸協(xié)議,它是基于TCP的一種應(yīng)用層協(xié)議,應(yīng)用層是什么樣的一個(gè)概念,通過下面這張示意圖可以很好的理解:

HTTP協(xié)議的網(wǎng)絡(luò)傳輸十分常見,例如網(wǎng)易的主頁(yè)http://www.163.com/。HTTP類型的網(wǎng)絡(luò)傳輸使用十分方便,但是其在安全性上卻有很大問題,列舉如下:
1.HTTP協(xié)議在傳輸數(shù)據(jù)時(shí)是明文的,任何人通過一個(gè)簡(jiǎn)單的抓包工具,就可以截獲到所有傳輸數(shù)據(jù)。
2.HTTP協(xié)議在傳輸數(shù)據(jù)時(shí)無(wú)法保證數(shù)據(jù)的完整,在截獲到明文數(shù)據(jù)后,很容易就可以將其篡改,這也是一些網(wǎng)頁(yè)總是被植入惡意廣告的原因。
3.HTTP協(xié)議在傳輸數(shù)據(jù)時(shí)無(wú)法保證真實(shí)性,這也是最恐怖的一點(diǎn)。誤入了域名欺騙的釣魚網(wǎng)站,極容易對(duì)用戶帶來財(cái)產(chǎn)損失。
基于上面3點(diǎn)安全性的考慮,一種更加安全的網(wǎng)絡(luò)傳輸協(xié)議勢(shì)必要推行,那就是HTTPS。
要理解HTTPS協(xié)議,首先需要明白什么是SSL/TLS。SSL全稱“Secure Sockets Layer”,意思為安全套接層。其實(shí)由網(wǎng)景公司為了解決HTTP傳輸協(xié)議在安全方面的缺陷而設(shè)計(jì)的。后來被標(biāo)準(zhǔn)化,更名為TLS,全稱“Transport Layer Security”,意思為傳輸層安全協(xié)議。
那么現(xiàn)在就好理解了,其實(shí)HTTPS就是將HTTP協(xié)議與TLS協(xié)議組合起來,在不改變HTTP協(xié)議原設(shè)計(jì)的基礎(chǔ)上,為其添加安全性校驗(yàn)并對(duì)傳輸?shù)臄?shù)據(jù)進(jìn)行加密。那么TLS究竟在網(wǎng)絡(luò)傳輸?shù)哪且粚舆M(jìn)行了處理了,下圖可以很好的表示:

三,https工作流程

1.Client發(fā)起一個(gè)HTTPS(比如https://juejin.im/user/5a9a9cdcf265da238b7d771c)的請(qǐng)求,根據(jù)RFC2818的規(guī)定,Client知道需要連接Server的443(默認(rèn))端口。
2.Server把事先配置好的公鑰證書(public key certificate)返回給客戶端。
3.Client驗(yàn)證公鑰證書:比如是否在有效期內(nèi),證書的用途是不是匹配Client請(qǐng)求的站點(diǎn),是不是在CRL吊銷列表里面,它的上一級(jí)證書是否有效,這是一個(gè)遞歸的過程,直到驗(yàn)證到根證書(操作系統(tǒng)內(nèi)置的Root證書或者Client內(nèi)置的Root證書)。如果驗(yàn)證通過則繼續(xù),不通過則顯示警告信息。
4.Client使用偽隨機(jī)數(shù)生成器生成加密所使用的對(duì)稱密鑰,然后用證書的公鑰加密這個(gè)對(duì)稱密鑰,發(fā)給Server。
5.Server使用自己的私鑰(private key)解密這個(gè)消息,得到對(duì)稱密鑰。至此,Client和Server雙方都持有了相同的對(duì)稱密鑰。
6.Server使用對(duì)稱密鑰加密“明文內(nèi)容A”,發(fā)送給Client。
7.Client使用對(duì)稱密鑰解密響應(yīng)的密文,得到“明文內(nèi)容A”。
8.Client再次發(fā)起HTTPS的請(qǐng)求,使用對(duì)稱密鑰加密請(qǐng)求的“明文內(nèi)容B”,然后Server使用對(duì)稱密鑰解密密文,得到“明文內(nèi)容B”。
四,HTTP 與 HTTPS 的區(qū)別
- HTTP 是明文傳輸協(xié)議,HTTPS 協(xié)議是由 SSL+HTTP 協(xié)議構(gòu)建的可進(jìn)行加密傳輸、身份認(rèn)證的網(wǎng)絡(luò)協(xié)議,比 HTTP 協(xié)議安全。

關(guān)于安全性,用最簡(jiǎn)單的比喻形容兩者的關(guān)系就是卡車運(yùn)貨,HTTP下的運(yùn)貨車是敞篷的,貨物都是暴露的。而https則是封閉集裝箱車,安全性自然提升不少。
- HTTPS比HTTP更加安全,對(duì)搜索引擎更友好,利于SEO,谷歌、百度優(yōu)先索引HTTPS網(wǎng)頁(yè);
- HTTPS需要用到SSL證書,而HTTP不用;
- HTTPS標(biāo)準(zhǔn)端口443,HTTP標(biāo)準(zhǔn)端口80;
- HTTPS基于傳輸層,HTTP基于應(yīng)用層;
- HTTPS在瀏覽器顯示綠色安全鎖,HTTP沒有顯示;
五、CA證書的作用,形象解釋
通過前面所介紹,我們知道HTTPS主要是為了解決3個(gè)問題:數(shù)據(jù)加密、數(shù)據(jù)完整、數(shù)據(jù)真實(shí)。那么下一步就是如何解決這些問題,數(shù)據(jù)加密在發(fā)送數(shù)據(jù)前依賴SSL層對(duì)數(shù)據(jù)進(jìn)行加密,數(shù)據(jù)完整與真實(shí)性則要靠另一種關(guān)鍵技術(shù):數(shù)字證書。
通過一個(gè)小例子可以很容易的理解證書的作用,這個(gè)例子的來源是<編程隨想>的作者,我這里暫且借用一下:A公司的a到B公司辦事,為了證明a確實(shí)是A公司的職員而不是商業(yè)間諜,A公司會(huì)為a提供一個(gè)帶有公章的證明,當(dāng)B公司看到這個(gè)證明時(shí),就可以信任辦事員a。對(duì)比網(wǎng)絡(luò)傳輸,這個(gè)證明就是證書,證書可以保證這個(gè)網(wǎng)站的真實(shí)性。我們繼續(xù)往后分析,當(dāng)B公司與越來越多的公司進(jìn)行商業(yè)合作時(shí),就又有新的問題出現(xiàn)了,比如C公司的c來B公司辦事,就需要拿C公司帶公章的證明,D公司的d來B公司辦事就需要拿D公司帶公章的證明...這樣一來,B公司要存放好多公司的公章和證明的模板,才能夠完成校驗(yàn)。這樣未免也太麻煩了,對(duì)應(yīng)到網(wǎng)絡(luò)傳輸中,客戶端就是B公司,各個(gè)網(wǎng)站都有自己的證書文件,這樣客戶端需要安裝信任大量的證書,為了解決這樣的問題,就有了第三方CA機(jī)構(gòu)。第三方CA機(jī)構(gòu)是由大家公認(rèn)信任的機(jī)構(gòu),例如R公司為第三方信任機(jī)構(gòu),其業(yè)務(wù)是為其他公司提供公章證明,這樣一來,B公司只要保有這個(gè)R公司的公章證明副本,其他A,C,D公司的辦事員也只需要從R公司申請(qǐng)到一個(gè)公章證明就可以到B公司來交流業(yè)務(wù)了。
CA的全稱是“Certificate Authority”,意為證書授權(quán)中心。大部分CA機(jī)構(gòu)頒發(fā)的證書都是需要付費(fèi)的,CA機(jī)構(gòu)頒發(fā)的證書一般都是根證書,根證書也比較容易理解,首先證書是有鏈?zhǔn)叫湃侮P(guān)系的,例如Y證書是由CA機(jī)構(gòu)頒發(fā)的根證書,由這個(gè)Y證書還可以創(chuàng)建出許多子證書,子證書可以繼續(xù)創(chuàng)建子證書,只要根證書是受信任的,其下所有的子證書都是受信任的,如下圖:

我們可以打開開源中國(guó)博客的主頁(yè):https://www.oschina.net/blog。在Chrome瀏覽器地址欄左邊可以查看證書信息,如下:

點(diǎn)擊證書信息,可以看到完整的證書鏈,如下圖:

從圖中可以看到,根證書是由CA機(jī)構(gòu)VerSign公司頒發(fā)的。此處還可以看到當(dāng)前證書是否有效以及過期時(shí)間,如果證書無(wú)效則說明此網(wǎng)頁(yè)信息有可能被篡改過,用戶在訪問時(shí)就要小心了。
除了CA機(jī)構(gòu)可以簽發(fā)證書外,個(gè)人其實(shí)也是可以創(chuàng)建證書的,當(dāng)然個(gè)人創(chuàng)建的證書也是不被信任的,我們姑且把這類證書叫做自簽名證書,如果用自簽名證書搭建了HTTPS的服務(wù),則客戶端需要安裝對(duì)應(yīng)的證書信任,才可以進(jìn)行此服務(wù)的訪問。后面我們會(huì)進(jìn)一步討論自簽名證書的使用。
六、搭建一個(gè)本地的HTTPS服務(wù)
使用Node.js可以快速的搭建前端服務(wù),我們這里使借助Express框架來搭建本地的HTTPS服務(wù),用于測(cè)試我們后邊將要進(jìn)行HTTPS通訊。Express搭建搭建項(xiàng)目模板的過程在以前的一篇博客中有詳細(xì)的介紹,這里就不再重復(fù)了,地址如下:
使用Express搭建前端項(xiàng)目:https://my.oschina.net/u/2340880/blog/794928。
根據(jù)前面所述,搭建HTTPS服務(wù)需要有證書憑證,兩種證書我們可以選擇,一種是CA機(jī)構(gòu)簽發(fā)的證書,還有一種是我們自己制作的自簽名證書,在Mac電腦上打開鑰匙串訪問應(yīng)用,打開其中的證書助理,如下圖所示:

選擇其中的為您自己創(chuàng)建證書選項(xiàng),如下圖:

在之后的界面中,輸入證書的名稱,選擇證書類型,如下圖所示:

上面,我把證書的名字創(chuàng)建成了琿少,身份類型選擇的是自簽名的根證書,證書類型選擇SSL服務(wù)器,之后點(diǎn)擊創(chuàng)建即可完成證書的創(chuàng)建。
創(chuàng)建完成后,在鑰匙串訪問的登錄證書中,可以看到已經(jīng)有了琿少這個(gè)自簽名的證書,如下圖:

在證書上點(diǎn)擊右鍵,選擇導(dǎo)出選項(xiàng),名字我將其取名為huishao,文件類型要選擇.p12,如下圖所示:

點(diǎn)擊存儲(chǔ)后,需要設(shè)置一個(gè)訪問密碼,這個(gè)密碼將來將用于從.p12文件中獲取證書和密鑰,如下圖所示:

之后,系統(tǒng)有可能會(huì)讓你再次輸入一個(gè)密碼,將入下圖所示,注意,這里需要輸入的是系統(tǒng)的登錄密碼:

完成上面操作后,我們已經(jīng)將一個(gè).p12文件導(dǎo)出到了桌面。那么這個(gè).p12文件到底是個(gè)什么東西呢,它和證書之間又有什么關(guān)系呢,其實(shí).p12文件一個(gè)復(fù)合文件,其中包裝了私鑰與證書信息,使用OpenSSL工具可以將其中的信息進(jìn)行提取,搭建一個(gè)HTTPS的服務(wù)器需要兩個(gè)文件,分別問證書文件和私鑰文件,下面我們來從.p12文件中提取這些需要的文件。
打開終端,cd到huishao.p12文件所在的目錄下,使用如下命令可以將.p12文件中的私鑰分解出來:
openssl pkcs12 -in huishao.p12 -nocerts -out privateKey.pem -nodes
之間會(huì)要求輸入導(dǎo)出.p12文件時(shí)所設(shè)置的密碼。
使用如下命令將.p12文件中的證書分解出來:
openssl pkcs12 -in huishao.p12 -nokeys -out cert.pem -nodes
之間也會(huì)要求輸入導(dǎo)出.p12文件時(shí)所設(shè)置的密碼。完成上面兩部操作后,可以看到當(dāng)前文件夾下多了兩個(gè)文件,分別為cert.pem與privateKey.pem,他們分別是證書文件與密鑰文件,將他們拷貝到Express項(xiàng)目的bin文件夾下,使得Express項(xiàng)目的結(jié)構(gòu)看起來如下圖所示:

下面我們來配置Express項(xiàng)目。
在生成好的Express項(xiàng)目中的www文件的末尾添加如下代碼:
/*
HTTPS
*/
var fs = require('fs');
var https = require('https');
/*
密鑰文件
*/
var privatekey = fs.readFileSync('./privateKey.pem', 'utf8');
/*
證書文件
*/
var certificate = fs.readFileSync('./cert.pem', 'utf8');
var options={key:privatekey, cert:certificate};
var serverHttps = https.createServer(options, app);
/*
綁定端口
*/
serverHttps.listen(8080,function () {
console.log('Https server listening on port ' + 8080);
});
用終端在bin文件夾下運(yùn)行 node www,效果如下:

在瀏覽器打開:https://localhost:8080/users,如果服務(wù)器搭建成功,Chrome中會(huì)出現(xiàn)如下效果:

點(diǎn)擊高級(jí),點(diǎn)擊其中的繼續(xù)訪問,可以正常獲取到服務(wù)器返回的數(shù)據(jù)。到此,我們的HTTPS服務(wù)就搭建成功了。
六、iOS開發(fā)中通過配置info.plist文件來允許HTTP協(xié)議類型的通訊
前面扯了太多,終于提到重點(diǎn)部分了。Apple在iOS9中就已經(jīng)漏出一些強(qiáng)制HTTPS通訊的端倪,只是給了開發(fā)者一些過渡,在iOS10及以后的審核機(jī)制中,Apple對(duì)于強(qiáng)制HTTPS的推動(dòng)將會(huì)越來越強(qiáng),如何讓自己的應(yīng)用程序盡快的適配HTTPS相關(guān)的標(biāo)準(zhǔn),是iOS開發(fā)者必須面對(duì)的任務(wù)。
通過前面的分析我們了解,CA機(jī)構(gòu)簽發(fā)的證書是被默認(rèn)信任的,這就是說,如果你的公司比較有錢,愿意花錢從CA機(jī)構(gòu)申請(qǐng)一個(gè)付費(fèi)的證書,那么很幸運(yùn),你的iOS工程是不需要做任何修改的,這些CA機(jī)構(gòu)簽發(fā)的證書是默認(rèn)受信任的,因此你可以直接在程序中進(jìn)行HTTPS類型的請(qǐng)求,所需要修改的只是將請(qǐng)求url改成https開頭。但是另一種情況,無(wú)論出于什么原因,你的后臺(tái)服務(wù)用的是自簽名的證書,就想我們上面搭建的HTTPS服務(wù)一樣,如果在不做任何處理的情況下在項(xiàng)目中訪問這樣的服務(wù),就會(huì)出現(xiàn)問題了,原因是我們自己創(chuàng)建的自簽名證書是不受信任的,系統(tǒng)默認(rèn)拒絕了請(qǐng)求,示例如下:
-(void)normalHttps{
NSURLRequest * req = [NSURLRequest requestWithURL:[NSURL URLWithString:@"https://localhost:8080/users"]];
NSURLSessionConfiguration * config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession * session = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:[NSOperationQueue mainQueue]];
[[session dataTaskWithRequest:req completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"%@,%@",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding],error);
}] resume];
}
運(yùn)行工程后可以看到,并沒有獲取到相關(guān)數(shù)據(jù),Xcode提示為:
NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9802)
好了,那么我們先不管HTTPS的問題,如果我們直接對(duì)HTTP協(xié)議的服務(wù)進(jìn)行請(qǐng)求,會(huì)不會(huì)有問題呢,將代碼修改如下:
-(void)normalHttps{
NSURLRequest * req = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://localhost:3000/users"]];
NSURLSessionConfiguration * config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession * session = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:[NSOperationQueue mainQueue]];
[[session dataTaskWithRequest:req completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"%@,%@",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding],error);
}] resume];
}
需要注意:Express在進(jìn)行項(xiàng)目模板的創(chuàng)建時(shí),會(huì)默認(rèn)幫我們綁定一個(gè)3000端口的HTTP服務(wù)。
運(yùn)行工程后,可以發(fā)現(xiàn)HTTP協(xié)議的請(qǐng)求也無(wú)法訪問,報(bào)錯(cuò)如下:
App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file.
其意思大致是說應(yīng)用程序傳輸安全要求強(qiáng)制使用HTTPS類型的服務(wù),但是開發(fā)者可以通過配置info.plsit文件來回避這一政策。這就是我們這節(jié)的重點(diǎn),通過文件配置的方式來跳過應(yīng)用安全傳輸協(xié)議。
在iOS9之后,開發(fā)者可以在Info.plist文件中添加如下鍵:NSAppTransportSecurity。這個(gè)鍵用來配置APP傳輸安全的相關(guān)策略,是字典類型,其中可以設(shè)置的鍵有五個(gè),如下:
NSAllowsArbitraryLoads:布爾值,默認(rèn)為NO,設(shè)置為YES則代表除了NSExceptionDomains中設(shè)置的域名外,其他所有請(qǐng)求的協(xié)議類型都不受限制,也就是說可以支持HTTP類型的請(qǐng)求,這個(gè)鍵的作用域是全局的,App內(nèi)所有的請(qǐng)求都受影響,但是如果開發(fā)者設(shè)置為了YES,在提交審核時(shí)需要說明原因。
NSAllowsArbitraryLoadsForMedia:布爾值,默認(rèn)為NO,設(shè)置為YES的話,則應(yīng)用程序內(nèi)所有的媒體數(shù)據(jù)的加載將不受協(xié)議類型的限制,同樣如果開發(fā)者設(shè)置為了YES,則在提交審核時(shí)需要說明原因。
NSAllowsArbitraryLoadsInWebContent:布爾值,默認(rèn)為NO。如果設(shè)置為YES,則應(yīng)用程序內(nèi)所有WebView的請(qǐng)求加載不受協(xié)議類型的限制,開發(fā)者設(shè)置為了YES,則在提交審核時(shí)需要說明原因。
NSAllowsLocalNetworking:布爾值,默認(rèn)為NO,如果設(shè)置為YES,則在加載本地資源時(shí)不受安全傳輸協(xié)議的限制。
NSExceptionDomains:字典,其主要對(duì)某些特殊域名做限制。其中結(jié)構(gòu)可以表示如下:
NSAppTransportSecurity : Dictionary {
NSAllowsArbitraryLoads : Boolean
NSAllowsArbitraryLoadsForMedia : Boolean
NSAllowsArbitraryLoadsInWebContent : Boolean
NSAllowsLocalNetworking : Boolean
//對(duì)某些域名做特殊限制
NSExceptionDomains : Dictionary {
<domain-name-string> : Dictionary {
NSIncludesSubdomains : Boolean
NSExceptionAllowsInsecureHTTPLoads : Boolean
NSExceptionMinimumTLSVersion : String
NSExceptionRequiresForwardSecrecy : Boolean // Default value is YES
NSRequiresCertificateTransparency : Boolean
}
}
}
NSIncludesSubdomains:布爾值,這個(gè)鍵的作用是設(shè)置此域名下的所有子域名是否采用和父域名相同的配置。
NSExceptionAllowsInsecureHTTPLoads:布爾值,設(shè)置是否允許此域名使用自簽名的證書進(jìn)行請(qǐng)求,默認(rèn)為NO,如果設(shè)置為YES,則在提交時(shí)需要說明原因。
NSExceptionMinimumTLSVersion:設(shè)置所使用的TLS版本。
NSExceptionRequiresForwardSecret:設(shè)置為NO,則不允許向前加密方式。
NSRequiresCertificateTransparency:如果設(shè)置為YES,則服務(wù)端的證書要有有效的透明時(shí)間戳。
七、iOS中使用自簽名的證書進(jìn)行HTTPS請(qǐng)求校驗(yàn)
通過Info.plist文件我們是可以繞過安全傳輸協(xié)議的,但是不幸的是,從文檔上看,無(wú)論開發(fā)者通過哪種方式來繞過安全傳輸協(xié)議,Apple都要求開發(fā)者在提審時(shí)提供合適的理由,這就是說:如果你使用了HTTP協(xié)議的請(qǐng)求,沒有充足理由的話,你的App有很大的可能被審核拒絕。因此,更加保險(xiǎn)的一種方式是將所有的服務(wù)都換成HTTPS協(xié)議的,如果有CA證書,當(dāng)然完事大吉,如果沒有,我們也可以通過驗(yàn)證自簽名證書的方式來適配HTTPS協(xié)議。
在進(jìn)行HTTPS請(qǐng)求時(shí),服務(wù)端會(huì)先將證書文件返回給客戶端,如果客戶端的證書信任列表中包含這個(gè)證書,則此請(qǐng)求可以正常進(jìn)行,如果沒有,則請(qǐng)求會(huì)被拒絕。因此,在iOS中適配自簽名證書的HTTPS請(qǐng)求實(shí)際上就是將這個(gè)自簽名的證書安裝進(jìn)客戶端的信任列表。iOS中需要使用的證書是der格式的,可以使用如下命令將pem格式的證書轉(zhuǎn)換成der格式的證書:
openssl x509 -inform PEM -in cert.pem -outform DER -out cert.der
將生成的cert文件添加進(jìn)工程中,修改請(qǐng)求如下:
-(void)normalHttps{
NSURLRequest * req = [NSURLRequest requestWithURL:[NSURL URLWithString:@"https://localhost:8080/users"]];
NSURLSessionConfiguration * config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession * session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionTask * task = [session dataTaskWithRequest:req completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"%@,%@",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding],error);
}];
[task resume];
}
除此之外,需要實(shí)現(xiàn)一個(gè)SURLSessionDelegate的協(xié)議方法如下:
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler {
NSLog(@"證書認(rèn)證");
//先判斷證書是否有效
if ([[[challenge protectionSpace] authenticationMethod] isEqualToString: NSURLAuthenticationMethodServerTrust]) {
//證書驗(yàn)證請(qǐng)求
SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust];
/**
* 導(dǎo)入多張CA證書(Certification Authority,支持SSL證書以及自簽名的CA)
*/
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"cert" ofType:@"der"];//自簽名證書
NSData* caCert = [NSData dataWithContentsOfFile:cerPath];
//可以添加多張證書
NSArray *caArray = @[caCert];
//驗(yàn)證規(guī)則
NSMutableArray *policies = [NSMutableArray array];
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
NSMutableArray *pinnedCertificates = [NSMutableArray array];
//進(jìn)行自簽名證書的添加
for (NSData *certificateData in caArray) {
[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
}
SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);
SecTrustResultType result = -1;
//通過本地導(dǎo)入的證書來驗(yàn)證服務(wù)器的證書是否可信
SecTrustEvaluate(serverTrust, &result);
NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
return [[challenge sender] useCredential: credential
forAuthenticationChallenge: challenge];
}
}
如上修改后,再次運(yùn)行工程,可以看到已經(jīng)成功請(qǐng)求到了HTTPS自簽名證書服務(wù)提供的數(shù)據(jù):

介于篇幅過長(zhǎng),關(guān)于NSURLAuthenticationChallenge相關(guān)類的更多探討和常用網(wǎng)絡(luò)庫(kù)AFNetworking中HTTPS的適配,下篇博客會(huì)繼續(xù)介紹。
八,CA證書與自簽名證書的異同
如果你想要構(gòu)建一個(gè)成功的網(wǎng)站,安全是關(guān)鍵因素之一,對(duì)于需要從訪問者那里收集PIA(personally identifiable information,個(gè)人識(shí)別信息)的網(wǎng)站而言,尤其如此。
考慮一個(gè)需要輸入社會(huì)保險(xiǎn)號(hào)的網(wǎng)站,或更常見的,需要向其添加信用卡信息以完成購(gòu)買行為的電子商務(wù)網(wǎng)站,在這樣的網(wǎng)站上,安全不僅僅是來自那些訪問者的期望,更是成功的關(guān)鍵。
如果你正在構(gòu)建一個(gè)電子商務(wù)網(wǎng)站,首先就需要一個(gè)安全證書以便保證服務(wù)器的數(shù)據(jù)安全,對(duì)于證書的選擇,即可以創(chuàng)建自簽名證書,也可以從證書頒發(fā)機(jī)構(gòu)(CA)獲得由其簽名的證書,讓我們看看這兩種證書的異同。
CA簽名的證書和自簽名證書的相似性
無(wú)論你的證書是由CA簽名的,還是自己簽名的,有一件事是完全相同的:你會(huì)得到一個(gè)安全的網(wǎng)站。通過HTTPS/SSL連接發(fā)送的數(shù)據(jù)將被加密,第三方無(wú)法竊聽。
既然自簽名證書也能做到這一點(diǎn),那為何要向CA付款呢?
CA告訴你的客戶:此服務(wù)器信息已由”信任源點(diǎn)“驗(yàn)證,最常用的CA是Verisign。CA會(huì)驗(yàn)證你的域名的所有權(quán)并頒發(fā)證書,這就能保證網(wǎng)站是安全而且合法的。
使用自簽名證書的問題是,幾乎每一個(gè)Web瀏覽器都會(huì)檢查HTTPS連接是否由可信的CA簽名,如果該連接是自簽名的,則會(huì)將其標(biāo)記為潛在風(fēng)險(xiǎn)并彈出錯(cuò)誤消息,你的客戶對(duì)該站點(diǎn)信任度就會(huì)降低。
簡(jiǎn)要總結(jié):CA簽名的證書兼具“身份證明”和“加密”雙重功能,而由于自證身份不可信,自簽名證書就只有加密功能,用于無(wú)需身份證明的場(chǎng)合。
在何種情況下可以使用自簽名證書?
由于它們提供了相同的保護(hù)能力,所以能在任何使用CA簽名證書的場(chǎng)合中使用自簽名證書,但在某些場(chǎng)合特別適用自簽名證書。例如,自簽名證書非常適合測(cè)試HTTPS服務(wù)器,你不必僅僅為測(cè)試網(wǎng)站就要支付CA簽名證書的費(fèi)用,只需提醒測(cè)試人員他們的瀏覽器可能彈出警告信息。
也可以在需要輸入隱私信息的情況下使用自簽名證書,例如:
●用戶名和密碼表單
●收集個(gè)人(非財(cái)務(wù))信息
當(dāng)然,只有那些了解并信任你的人才會(huì)使用這樣的網(wǎng)站。
所以你看到了,歸根結(jié)底就是“信任”二字。當(dāng)你使用自簽名證書時(shí),你是在對(duì)客戶說:“請(qǐng)相信我——我就是我說的我”;當(dāng)你使用由CA簽名的證書時(shí),你是在說:“請(qǐng)相信我——因?yàn)閂erisign可以證明我的身份”。
如果你在做電子商務(wù),就需要一個(gè)CA簽名證書。
如果你使用自簽名證書只是為了客戶登錄你的網(wǎng)站,那么他們可能會(huì)原諒你,但如果要求他們輸入信用卡或Paypal的信息,那么你真的需要一個(gè)CA簽名的證書,因?yàn)榇蠖鄶?shù)人信任CA簽名的證書,如果沒有它,就不會(huì)通過HTTPS服務(wù)器做生意。所以如果你想在你的網(wǎng)站上賣東西,那就投資于證書吧,這只是做生意的成本。

有TrustAsia Technologies,Inc 的簽發(fā)機(jī)構(gòu),就是
HTTPS請(qǐng)求
(1)證書轉(zhuǎn)換
進(jìn)到證書路徑,執(zhí)行openssl x509 -in 證書.crt -out 證書.cer -outform der語(yǔ)句,這樣就可以得到cer類型的證書了。雙擊,導(dǎo)入電腦。
(2)證書放入工程
a.可以直接把轉(zhuǎn)換好的cer文件拖動(dòng)到工程中。
b.可以在鑰匙串內(nèi),找到你導(dǎo)入的證書,單擊右鍵,導(dǎo)出項(xiàng)目,就可以導(dǎo)出.cer文件的證書了
(3)配置Info.plist
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
(6)使用AFNetworking 3.x版本請(qǐng)求HTTPS
a.校驗(yàn)證書
// 初始化單例類
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.securityPolicy.SSLPinningMode = AFSSLPinningModeCertificate;
// 設(shè)置證書模式
NSString * cerPath = [[NSBundle mainBundle] pathForResource:@"xxx" ofType:@"cer"];
NSData * cerData = [NSData dataWithContentsOfFile:cerPath];
manager.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:[[NSSet alloc] initWithObjects:cerData, nil]];
// 客戶端是否信任非法證書
mgr.securityPolicy.allowInvalidCertificates = YES;
// 是否在證書域字段中驗(yàn)證域名
[mgr.securityPolicy setValidatesDomainName:NO];
b.不校驗(yàn)證書
// 初始化單例類
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
// 設(shè)置非校驗(yàn)證書模式
manager.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
manager.securityPolicy.allowInvalidCertificates = YES;
[manager.securityPolicy setValidatesDomainName:NO];
iOS開發(fā)中實(shí)現(xiàn)支持HTTPS,有兩種方法:一是后臺(tái)那邊都處理好了,移動(dòng)端直接可以使用HTTPS接口,二是后臺(tái)給移動(dòng)端一個(gè)服務(wù)器證書cer 文件,這時(shí)我們就需要將cer文件導(dǎo)入到我們的工程中,以下是實(shí)現(xiàn)方法
1. 雙擊證書,這時(shí)證書已經(jīng)添加到了鑰匙串中
2. 將cer 文件拖入工程中
3. 如果使用的是AFNetwotking 的話,在代碼中添加以下代碼
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
//證書
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
manager.securityPolicy = securityPolicy;
// 2.設(shè)置證書模式
NSString * cerPath = [[NSBundle mainBundle] pathForResource:@"tomcat" ofType:@"cer"]; //tomcat是cer文件的名稱
NSData * cerData = [NSData dataWithContentsOfFile:cerPath];
manager.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:[[NSSet alloc] initWithObjects:cerData, nil]];
// 客戶端是否信任非法證書
manager.securityPolicy.allowInvalidCertificates = YES;
// 是否在證書域字段中驗(yàn)證域名
[manager.securityPolicy setValidatesDomainName:NO];
至此就已經(jīng)完成支持HTTPS了
在一般的項(xiàng)目中,可以直接判斷如果允許自簽名證書的話,直接完全信任服務(wù)器證書,如果不允許自簽名證書的話,直接拿本地的證書和服務(wù)器的證書比較
+ (void)setupFreshNetwork:(BOOL)allowInvalidCertificates{
AFHTTPSessionManager *sessionManager =[ [AFHTTPSessionManager alloc] init];
if (!allowInvalidCertificates) {
sessionManager.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
sessionManager.securityPolicy.validatesDomainName = YES;
sessionManager.securityPolicy.allowInvalidCertificates = NO;
} else {
//是否允許無(wú)效證書(也就是自建的證書),默認(rèn)為NO 如果是需要驗(yàn)證自建證書,需要設(shè)置為YES
sessionManager.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone]; //完全信任服務(wù)器證書;
sessionManager.securityPolicy.validatesDomainName = NO;
sessionManager.securityPolicy.allowInvalidCertificates = YES;
}
}
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode {
NSSet <NSData *> *defaultPinnedCertificates = [self certificatesInBundle:[NSBundle mainBundle]];
return [self policyWithPinningMode:pinningMode withPinnedCertificates:defaultPinnedCertificates];
}
這個(gè)函數(shù)自動(dòng)在本地的目錄查找.cer 文件