HTTPS
HTTPS 連接建立過(guò)程大致是,客戶端和服務(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)單過(guò)程,中間還有很多細(xì)節(jié),詳細(xì)過(guò)程和原理已經(jīng)有很多文章闡述得很好,就不再?gòu)?fù)述,推薦一些相關(guān)文章:
關(guān)于非對(duì)稱加密算法的原理:RSA算法原理<一>、<二>
關(guān)于整個(gè)流程:HTTPS那些事<一>、<二>、<三>
關(guān)于數(shù)字證書:淺析數(shù)字證書
這里說(shuō)下一開始我比較費(fèi)解的兩個(gè)問題:
1.證書是怎樣驗(yàn)證的?怎樣保證中間人不能偽造證書?
首先要知道非對(duì)稱加密算法的特點(diǎn),非對(duì)稱加密有一對(duì)公鑰私鑰,用公鑰加密的數(shù)據(jù)只能通過(guò)對(duì)應(yīng)的私鑰解密,用私鑰加密的數(shù)據(jù)只能通過(guò)對(duì)應(yīng)的公鑰解密。
我們來(lái)看最簡(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算法,略過(guò)。)
建 立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ì)比,若相等就通過(guò)驗(yàn)證。整個(gè)流程大致是:F->CA私鑰加密->F1->客戶端CA公鑰解密->F。 因?yàn)橹虚g人不會(huì)有CA機(jī)構(gòu)的私鑰,客戶端無(wú)法通過(guò)CA公鑰解密,所以偽造的證書肯定無(wú)法通過(guò)驗(yàn)證。
2.什么是SSL Pinning?
可 以理解為證書綁定,是指客戶端直接保存服務(wù)端的證書,建立https連接時(shí)直接對(duì)比服務(wù)端返回的和客戶端保存的兩個(gè)證書是否一樣,一樣就表明證書 是真的,不再去系統(tǒng)的信任證書機(jī)構(gòu)里尋找驗(yàn)證。這適用于非瀏覽器應(yīng)用,因?yàn)闉g覽器跟很多未知服務(wù)端打交道,無(wú)法把每個(gè)服務(wù)端的證書都保存到本地,但CS架 構(gòu)的像手機(jī)APP事先已經(jīng)知道要進(jìn)行通信的服務(wù)端,可以直接在客戶端保存這個(gè)服務(wù)端的證書用于校驗(yàn)。
為什么直接對(duì)比就能保證證書沒問題? 如果中間人從客戶端取出證書,再偽裝成服務(wù)端跟其他客戶端通信,它發(fā)送給客戶端的這個(gè)證書不就能通過(guò)驗(yàn)證嗎?確 實(shí)可以通過(guò)驗(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ā)證書,這樣就無(wú)法通過(guò)系統(tǒng)受信任的CA機(jī)構(gòu)列表驗(yàn)證這個(gè)證書的真?zhèn)瘟?,所以需要SSL Pinning這樣的方式去驗(yàn)證。
AFNetworking中AFSecurityPolicy分三種驗(yàn)證模式:
AFSSLPinningModeNone
這個(gè)模式表示不做SSL pinning,只跟瀏覽器一樣在系統(tǒng)的信任機(jī)構(gòu)列表里驗(yàn)證服務(wù)端返回的證書。若證書是信任機(jī)構(gòu)簽發(fā)的就會(huì)通過(guò),若是自己服務(wù)器生成的證書,這里是不會(huì)通過(guò)的。
AFSSLPinningModeCertificate
這個(gè)模式表示用證書綁定方式驗(yàn)證證書,需要客戶端保存有服務(wù)端的證書拷貝,這里驗(yàn)證分兩步,第一步驗(yàn)證證書的域名/有效期等信息,第二步是對(duì)比服務(wù)端返回的證書跟客戶端返回的是否一致。
這里還沒弄明白第一步的驗(yàn)證是怎么進(jìn)行的,代碼上跟去系統(tǒng)信任機(jī)構(gòu)列表里驗(yàn)證一樣調(diào)用了SecTrustEvaluate,只是這里的列表?yè)Q成了客戶端保存的那些證書列表。若要驗(yàn)證這個(gè),是否應(yīng)該把服務(wù)端證書的頒發(fā)機(jī)構(gòu)根證書也放到客戶端里?
AFSSLPinningModePublicKey
這個(gè)模式同樣是用證書綁定方式驗(yàn)證,客戶端要有服務(wù)端的證書拷貝,只是驗(yàn)證時(shí)只驗(yàn)證證書里的公鑰,不驗(yàn)證證書的有效期等信息。只要公鑰是正確的,就能保證通信不會(huì)被竊聽,因?yàn)橹虚g人沒有私鑰,無(wú)法解開通過(guò)公鑰加密的數(shù)據(jù)。
1.生成服務(wù)器的KEY和證書簽名
openssl genrsa -des3 -out server.key 1024
openssl req -new -key server.key -out server.csr
openssl rsa -in server.key -out server_nopwd.key
openssl x509 -req -days 365 -in server.csr -signkey server_nopwd.key -out server.crt
2.證書格式轉(zhuǎn)換 由于iOS端Apple的API需要der格式證書,故用如下命令轉(zhuǎn)換
openssl x509 -outform der -in server.crt -out client.der
3.nginx配置文件修改
server {
listen 80;#HTTP默認(rèn)端口80
server_name tv.diveinedu.com;#主機(jī)名,與HTTP請(qǐng)求頭域的HOST匹配
access_log /var/log/nginx/tv.diveinedu.com.log;#訪問日志路徑
return 301 https://$server_name$request_uri;#強(qiáng)制把所有http訪問跳轉(zhuǎn)到https
}
server {
listen 443;#HTTPS默認(rèn)端口443
ssl on;#打開SSL安全Socket
ssl_certificate /usr/local/etc/nginx/server.crt;#證書文件路徑
ssl_certificate_key /usr/local/etc/nginx/server_nopwd.key;#私鑰文件路徑
#server_name xxx.com;#主機(jī)名,與HTTP請(qǐng)求頭域的HOST匹配
access_log logs/host.access.log;#訪問日志路徑
location / {
root /var/www/;#網(wǎng)站文檔根目錄
index index.php index.html;#默認(rèn)首頁(yè)
}
}
4.示例代碼
_manager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSString *certFilePath = [[NSBundle mainBundle] pathForResource:@"client" ofType:@"der"];
NSData *certData = [NSData dataWithContentsOfFile:certFilePath];
NSSet *certSet = [NSSet setWithObject:certData];
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey withPinnedCertificates:certSet];
securityPolicy.allowInvalidCertificates = YES;
securityPolicy.validatesDomainName = NO;
_manager.securityPolicy = securityPolicy;
AFHTTPResponseSerializer *serializer = [[AFHTTPResponseSerializer alloc] init];
serializer.acceptableContentTypes = [NSSet setWithObjects:@"text/html", nil];
_manager.responseSerializer = serializer;
[_manager GET:@"https://192.168.47.112/" parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"%@", [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding]);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"%@",[error localizedDescription]);
}];