AFNetworking3.0 HTTPS請求

weixin_34320159發表於2016-06-24

最近公司把網路請求從HTTP改成HTTPS 加密 證書這些東西 在這裡整理一下

HTTPS

HTTPS連線建立過程大致是,客戶端和服務端建立一個連線,服務端返回一個證書,客戶端裡存有各個受信任的證書機構根證書,用這些根證書對服務端 返回的證書進行驗證,經驗證如果證書是可信任的,就生成一個pre-master secret,用這個證書的公鑰加密後傳送給服務端,服務端用私鑰解密後得到pre-master secret,再根據某種演算法生成master secret,客戶端也同樣根據這種演算法從pre-master secret生成master secret,隨後雙方的通訊都用這個master secret對傳輸資料進行加密解密。

1.證書是怎樣驗證的?怎樣保證中間人不能偽造證書?

首先要知道非對稱加密演算法的特點,非對稱加密有一對公鑰私鑰,用公鑰加密的資料只能通過對應的私鑰解密,用私鑰加密的資料只能通過對應的公鑰解密。

我們來看最簡單的情況:一個證書頒發機構(CA),頒發了一個證書A,伺服器用這個證書建立https連線。客戶端在信任列表裡有這個CA機構的根證書。

首先CA機構頒發的證書A裡包含有證書內容F,以及證書加密內容F1,加密內容F1就是用這個證書機構的私鑰對內容F加密的結果。(這中間還有一次hash演算法,略過。)

建立https連線時,服務端返回證書A給客戶端,客戶端的系統裡的CA機構根證書有這個CA機構的公鑰,用這個公鑰對證書A的加密內容F1解密得 到F2,跟證書A裡內容F對比,若相等就通過驗證。整個流程大致是:F->CA私鑰加密->F1->客戶端CA公鑰解密->F。 因為中間人不會有CA機構的私鑰,客戶端無法通過CA公鑰解密,所以偽造的證書肯定無法通過驗證。

2.什麼是SSL Pinning?

可以理解為證書繫結,是指客戶端直接儲存服務端的證書,建立https連線時直接對比服務端返回的和客戶端儲存的兩個證書是否一樣,一樣就表明證書 是真的,不再去系統的信任證書機構裡尋找驗證。這適用於非瀏覽器應用,因為瀏覽器跟很多未知服務端打交道,無法把每個服務端的證書都儲存到本地,但CS架 構的像手機APP事先已經知道要進行通訊的服務端,可以直接在客戶端儲存這個服務端的證書用於校驗。

為什麼直接對比就能保證證書沒問題?如果中間人從客戶端取出證書,再偽裝成服務端跟其他客戶端通訊,它傳送給客戶端的這個證書不就能通過驗證嗎?確 實可以通過驗證,但後續的流程走不下去,因為下一步客戶端會用證書裡的公鑰加密,中間人沒有這個證書的私鑰就解不出內容,也就截獲不到資料,這個證書的私 鑰只有真正的服務端有,中間人偽造證書主要偽造的是公鑰。

為什麼要用SSL Pinning?正常的驗證方式不夠嗎?如果服務端的證書是從受信任的的CA機構頒發的,驗證是沒問題的,但CA機構頒發證書比較昂貴,小企業或個人使用者 可能會選擇自己頒發證書,這樣就無法通過系統受信任的CA機構列表驗證這個證書的真偽了,所以需要SSL Pinning這樣的方式去驗證。

3.利用AFNetworking完成HTTPS請求 (AFSecurityPolicy)

首先把證書放在工程的根目錄下

AFNetworking部分

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
        
securityPolicy.allowInvalidCertificates = YES;
securityPolicy.validatesDomainName = YES;
        
manager.securityPolicy = securityPolicy;

我遇到的坑:

1.每次請求都崩潰原因是往陣列裡加了空物件

斷點跟程式碼
AFNetworking的AFSecurityPolicy.m裡

for (NSData *certificateData in self.pinnedCertificates) {
    [pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
}

在這裡一直崩潰
原因是因為本地的那個證書裡沒有公鑰!!!!

//從本地證書裡取public key
static id AFPublicKeyForCertificate(NSData *certificate) {
    id allowedPublicKey = nil;
    
    ...
    
    // 這裡的allowedPublicKey 一直是空!! 
    return allowedPublicKey;
}

換了一個正確的證書之後 這個問題解決了 但是出別的問題

證書總是驗證不過 (本地證書沒問題 伺服器給的公鑰也OK)

if (!AFServerTrustIsValid(serverTrust)) {
    return NO;
}

總是返回NO

跟進去

static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {
    BOOL isValid = NO;
    SecTrustResultType result;
    __Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out);
    //kSecTrustResultUnspecified:證書通過驗證,但使用者沒有設定這些證書是否被信任
    //kSecTrustResultProceed:證書通過驗證,使用者有操作設定了證書被信任,例如在彈出的是否信任的alert框中選擇always trust
    isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
    // 總是返回5  也就是kSecTrustResultRecoverableTrustFailure
_out:
    return isValid;
}

原因:是因為Charles的問題!!! 就這個問題糾結了好久 偶然間發現的

參考:
http://www.jianshu.com/p/c6a903da8346
http://my.oschina.net/vimfung/blog/494687
http://www.cocoachina.com/ios/20140916/9632.html

相關文章