AFNetWorking源碼之AFSecurityPolicy

1 HTTPS以及SSL/TSL

SSL(Secure Sockets Layer, 安全套接字層),由於原先互聯網上使用的HTTP協議是明文的,存在不少缺點,好比傳輸內容會被偷窺和篡改。SSL協議的做用就是在傳輸層對網絡鏈接進行加密。html

到了1999年,SSL 由於應用普遍,已經成爲互聯網上的事實標準。IETF就在那年把SSL標準化。標準化以後的名稱改成 TLS(Transport Layer Security,傳輸層安全協議)。SSL與TLS能夠視做同一個東西的不一樣階段。git

簡單來講,HTTPS = HTTP + SSL/TLS, 也就是HTTP over SSLHTTP over TLS,這是後面加S的由來。github

HTTPS和HTTP異同:HTTP和HTTPS使用的是徹底不一樣的鏈接方式,用的端口也不同,前者是80,後者是443。HTTP的鏈接很簡單,是無狀態的;HTTPS協議是由SSL+HTTP協議構建的可進行加密傳輸、身份認證的網絡協議,比HTTP協議安全。數組

2 HTTPS的握手

img

1客戶端發出請求(ClientHello)

首先,客戶端(一般是瀏覽器)先向服務器發出加密通訊的請求,這被叫作ClientHello請求。在這一步,客戶端主要向服務器提供如下信息。瀏覽器

  • (1)支持的協議版本,好比TLS1.0版。安全

  • (2)一個客戶端生成的隨機數,稍後用於生成"對話密鑰"。服務器

  • (3)支持的加密方法,好比RSA公鑰加密。網絡

  • (4)支持的壓縮方法。架構

2服務器迴應(SeverHello)

服務器收到客戶端請求後,向客戶端發出迴應,這叫作SeverHello。服務器的迴應包含如下內容。app

  • (1)確認使用的加密通訊協議版本,好比TLS1.0版本。若是瀏覽器與服務器支持的版本不一致,服務器關閉加密通訊。

  • (2)一個服務器生成的隨機數,稍後用於生成"對話密鑰"。

  • (3)確認使用的加密方法,好比RSA公鑰加密。

  • (4)服務器證書。

3客戶端迴應

客戶端收到服務器迴應之後,首先驗證服務器證書。若是證書不是可信機構頒佈、或者證書中的域名與實際域名不一致、或者證書已通過期,就會向訪問者顯示一個警告,由其選擇是否還要繼續通訊。若是證書沒有問題,客戶端就會從證書中取出服務器的公鑰。而後,向服務器發送下面三項信息。

  • (1)一個隨機數。該隨機數用服務器公鑰加密,防止被竊聽。

  • (2)編碼改變通知,表示隨後的信息都將用雙方商定的加密方法和密鑰發送。

  • (3)客戶端握手結束通知,表示客戶端的握手階段已經結束。這一項同時也是前面發送的全部內容的hash值,用來供服務器校驗。

上面第一項的隨機數,是整個握手階段出現的第三個隨機數,又稱"pre-master key"。有了它之後,客戶端和服務器就同時有了三個隨機數,接着雙方就用事先商定的加密方法,各自生成本次會話所用的同一把"會話密鑰"。

4服務器的最後迴應

服務器收到客戶端的第三個隨機數pre-master key以後,計算生成本次會話所用的"會話密鑰"。而後,向客戶端最後發送下面信息。

  • (1)編碼改變通知,表示隨後的信息都將用雙方商定的加密方法和密鑰發送。

  • (2)服務器握手結束通知,表示服務器的握手階段已經結束。這一項同時也是前面發送的全部內容的hash值,用來供客戶端校驗。

至此,整個握手階段所有結束。接下來,客戶端與服務器進入加密通訊,就徹底是使用普通的HTTP協議,只不過用"會話密鑰"加密內容。

3 數字證書

上面握手階段的第二步服務器給客戶端的證書就是數字證書,該證書包含了公鑰等信息,通常是由服務器發給客戶端,接收方經過驗證這個證書是否是由信賴的CA簽發,或者與本地的證書相對比,來判斷證書是否可信;假如須要雙向驗證,則服務器和客戶端都須要發送數字證書給對方驗證。

數字證書是一個電子文檔,其中包含了持有者的信息、公鑰以及證實該證書有效的數字簽名。而數字證書以及相關的公鑰管理和驗證等技術組成了PKI(公鑰基礎設施)規範體系。通常來講,數字證書是由數字證書認證機構(Certificate authority,即CA)來負責簽發和管理,並承擔PKI體系中公鑰合法性的檢驗責任;數字證書的類型有不少,而HTTPS使用的是SSL證書。

怎麼來驗證數字證書是由CA簽發的,而不是第三方僞造的呢? 在回答這個問題前,咱們須要先了解CA的組織結構。首先,CA組織結構中,最頂層的就是根CA,根CA下能夠受權給多個二級CA,而二級CA又能夠受權多個三級CA,因此CA的組織結構是一個樹結構。對於SSL證書市場來講,主要被Symantec(旗下有VeriSign和GeoTrust)、Comodo SSL、Go Daddy 和 GlobalSign 瓜分。 瞭解了CA的組織結構後,來看看數字證書的簽發流程:

img

數字證書的簽發機構CA,在接收到申請者的資料後進行覈對並肯定信息的真實有效,而後就會製做一份符合X.509標準的文件。證書中的證書內容包括了持有者信息和公鑰等都是由申請者提供的,而數字簽名則是CA機構對證書內容進行hash加密後等到的,而這個數字簽名就是咱們驗證證書是不是有可信CA簽發的數據。

img

接收端接到一份數字證書Cer1後,對證書的內容作Hash等到H1;而後在簽發該證書的機構CA1的數字證書中找到公鑰,對證書上數字簽名進行解密,獲得證書Cer1簽名的Hash摘要H2;對比H1和H2,假如相等,則表示證書沒有被篡改。但這個時候仍是不知道CA是不是合法的,咱們看到上圖中有CA機構的數字證書,這個證書是公開的,全部人均可以獲取到。而這個證書中的數字簽名是上一級生成的,因此能夠這樣一直遞歸驗證下去,直到根CA。根CA是自驗證的,即他的數字簽名是由本身的私鑰來生成的。合法的根CA會被瀏覽器和操做系統加入到權威信任CA列表中,這樣就完成了最終的驗證。因此,必定要保護好本身環境(瀏覽器/操做系統)中根CA信任列表,信任了根CA就表示信任全部根CA下全部子級CA所簽發的證書,不要隨便添加根CA證書。

4 SSL Pinning

能夠理解爲證書綁定,是指客戶端直接保存服務端的證書,創建https鏈接時直接對比服務端返回的和客戶端保存的兩個證書是否同樣,同樣就代表證書是真的,再也不去系統的信任證書機構裏尋找驗證。這適用於非瀏覽器應用,由於瀏覽器跟不少未知服務端打交道,沒法把每一個服務端的證書都保存到本地,但CS架構的像手機APP事先已經知道要進行通訊的服務端,能夠直接在客戶端保存這個服務端的證書用於校驗。

爲何直接對比就能保證證書沒問題?若是中間人從客戶端取出證書,再假裝成服務端跟其餘客戶端通訊,它發送給客戶端的這個證書不就能經過驗證嗎?確實能夠經過驗證,但後續的流程走不下去,由於下一步客戶端會用證書裏的公鑰加密,中間人沒有這個證書的私鑰就解不出內容,也就截獲不到數據,這個證書的私鑰只有真正的服務端有,中間人僞造證書主要僞造的是公鑰。

爲何要用SSL Pinning?正常的驗證方式不夠嗎?若是服務端的證書是從受信任的的CA機構頒發的,驗證是沒問題的,但CA機構頒發證書比較昂貴,小企業或我的用戶可能會選擇本身頒發證書,這樣就沒法經過系統受信任的CA機構列表驗證這個證書的真僞了,因此須要SSL Pinning這樣的方式去驗證。

5 iOS的HTTPS請求

下面我會實現自簽名證書(12306)、SSL信任證書(baidu)、系統證書(蘋果)三種狀況的實現來看他們的區別,百度和12306的證書已經被我下載到個人項目裏面了,具體能夠去Demo裏面看實現過程。

1 自簽名證書

咱們手動指定securityPolicy認證屬性。經過12306證書來實現。

//自建證書認證
- (IBAction)buttion1:(id)sender {
    NSURL *url = [NSURL URLWithString:@"https://kyfw.12306.cn/otn/leftTicket/init"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
   // [request setValue:@"text/html" forHTTPHeaderField:@"Accept"];
    AFURLSessionManager *manager = [[AFURLSessionManager alloc]initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
    //指定安全策略
    manager.securityPolicy = [self ticketSecurityPolicy];
    //指定返回數據類型,默認是AFJSONResponseSerializer類型,猶豫這裏不是JSON類型的返回數據,因此須要手動指定返回類型
    AFHTTPResponseSerializer *responseSerializer = [AFHTTPResponseSerializer serializer];
    responseSerializer.acceptableContentTypes = [NSSet setWithObject:@"text/html"];
    manager.responseSerializer = responseSerializer;
    NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
        NSLog(@"%@-----%@",[[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding],error);
    }];
    [dataTask resume];

}
/**
 12306的認證證書,他的認證證書是自簽名的

 @return 返回指定的認證策略
 */
-(AFSecurityPolicy*)ticketSecurityPolicy {
    // /先導入證書
    NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"12306" ofType:@"cer"];//證書的路徑
    NSData *certData = [NSData dataWithContentsOfFile:cerPath];
    NSSet *set = [NSSet setWithObject:certData];
    
    AFSecurityPolicy *securityPolicy;
    if (true) {
        securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:set];
    }else{
        // AFSSLPinningModeCertificate 使用證書驗證模式。下面這個方法會默認使用項目裏面的全部證書
        securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
    }
    // allowInvalidCertificates 是否容許無效證書(也就是自建的證書),默認爲NO
    // 若是是須要驗證自建證書,須要設置爲YES
    securityPolicy.allowInvalidCertificates = YES;
    
    //validatesDomainName 是否須要驗證域名,默認爲YES;
    //假如證書的域名與你請求的域名不一致,需把該項設置爲NO;如設成NO的話,即服務器使用其餘可信任機構頒發的證書,也能夠創建鏈接,這個很是危險,建議打開。
    //置爲NO,主要用於這種狀況:客戶端請求的是子域名,而證書上的是另一個域名。由於SSL證書上的域名是獨立的,假如證書上註冊的域名是www.google.com,那麼mail.google.com是沒法驗證經過的;固然,有錢能夠註冊通配符的域名*.google.com,但這個仍是比較貴的。
    //如置爲NO,建議本身添加對應域名的校驗邏輯。
    securityPolicy.validatesDomainName = NO;
    
    return securityPolicy;
}

2 SSL信任證書

咱們手動指定securityPolicy認證屬性。經過百度證書來實現。

//認證證書認證
- (IBAction)button2:(id)sender {
    NSURL *url = [NSURL URLWithString:@"https://www.baidu.com"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    //[request setValue:@"text/html" forHTTPHeaderField:@"Accept"];
    AFURLSessionManager *manager = [[AFURLSessionManager alloc]initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
    //指定安全策略
    manager.securityPolicy = [self baiduSecurityPolicy];
    //指定返回數據類型,默認是AFJSONResponseSerializer類型,猶豫這裏不是JSON類型的返回數據,因此須要手動指定返回類型
    AFHTTPResponseSerializer *responseSerializer = [AFHTTPResponseSerializer serializer];
    responseSerializer.acceptableContentTypes = [NSSet setWithObject:@"text/html"];
    manager.responseSerializer = responseSerializer;
    NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
        NSLog(@"%@-----%@",[[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding],error);
    }];
    [dataTask resume];
}

/**
百度的的認證證書,他的認證證書是花錢買的,也就是否是自簽名的證書。這種證書,若是咱們要手動指定,pinmode只能是`AFSSLPinningModeNone`
 
 @return 返回指定的認證策略
 */
-(AFSecurityPolicy*)baiduSecurityPolicy {
    // /先導入證書
    NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"baidu" ofType:@"cer"];//證書的路徑
    NSData *certData = [NSData dataWithContentsOfFile:cerPath];
    NSSet *set = [NSSet setWithObject:certData];
    
    AFSecurityPolicy *securityPolicy;
    if (true) {
        //這裏只能用AFSSLPinningModeNone才能成功,並且我係統的證書列表裏面已經有百度的證書了
        securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone withPinnedCertificates:set];
    }else{
        // AFSSLPinningModeCertificate 使用證書驗證模式。下面這個方法會默認使用項目裏面的全部證書
        securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
    }
    // allowInvalidCertificates 是否容許無效證書(也就是自建的證書),默認爲NO
    // 若是是須要驗證自建證書,須要設置爲YES
    securityPolicy.allowInvalidCertificates = NO;
    
    //validatesDomainName 是否須要驗證域名,默認爲YES;
    //假如證書的域名與你請求的域名不一致,需把該項設置爲NO;如設成NO的話,即服務器使用其餘可信任機構頒發的證書,也能夠創建鏈接,這個很是危險,建議打開。
    //置爲NO,主要用於這種狀況:客戶端請求的是子域名,而證書上的是另一個域名。由於SSL證書上的域名是獨立的,假如證書上註冊的域名是www.google.com,那麼mail.google.com是沒法驗證經過的;固然,有錢能夠註冊通配符的域名*.google.com,但這個仍是比較貴的。
    //如置爲NO,建議本身添加對應域名的校驗邏輯。
    securityPolicy.validatesDomainName = YES;
    
    return securityPolicy;
}

3 SSL證書AFN默認處理

這裏咱們不作任何額外的處理,直接使用AFN的默認證書處理機制。經過AFURLSessionManagersecurityPolicy默認實現。它會和存在系統中的作對比來驗證證書。

//系統證書認證
- (IBAction)button3:(id)sender {
    NSURL *url = [NSURL URLWithString:@"https://www.apple.com/"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    AFURLSessionManager *manager = [[AFURLSessionManager alloc]initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
    //指定返回數據類型,默認是AFJSONResponseSerializer類型,猶豫這裏不是JSON類型的返回數據,因此須要手動指定返回類型
    AFHTTPResponseSerializer *responseSerializer = [AFHTTPResponseSerializer serializer];
    responseSerializer.acceptableContentTypes = [NSSet setWithObject:@"text/html"];
    manager.responseSerializer = responseSerializer;
    NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
        NSLog(@"%@-----%@",[[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding],error);
    }];
    [dataTask resume];
}

6 AFSecurityPolicy源碼解析

AFSecurityPolicy分三種驗證模式

  • AFSSLPinningModeNone:

    這個模式表示不作SSL pinning,只跟瀏覽器同樣在系統的信任機構列表裏驗證服務端返回的證書。若證書是信任機構簽發的就會經過,如果本身服務器生成的證書,這裏是不會經過的。
  • AFSSLPinningModeCertificate:

    這個模式表示用證書綁定方式驗證證書,須要客戶端保存有服務端的證書拷貝,這裏驗證分兩步,第一步驗證證書的域名/有效期等信息,第二步是對比服務端返回的證書跟客戶端返回的是否一致。這裏還沒弄明白第一步的驗證是怎麼進行的,代碼上跟去系統信任機構列表裏驗證同樣調用了SecTrustEvaluate,只是這裏的列表換成了客戶端保存的那些證書列表。若要驗證這個,是否應該把服務端證書的頒發機構根證書也放到客戶端裏?
  • AFSSLPinningModePublicKey:

    這個模式一樣是用證書綁定方式驗證,客戶端要有服務端的證書拷貝,只是驗證時只驗證證書裏的公鑰,不驗證證書的有效期等信息。只要公鑰是正確的,就能保證通訊不會被竊聽,由於中間人沒有私鑰,沒法解開經過公鑰加密的數據。

SecTrustRef

這是一個須要驗證的信任對象,包含待驗證的證書和支持的驗證方法等。

SecTrustResultType

表示驗證結果。其中 kSecTrustResultProceed表示serverTrust驗證成功,且該驗證獲得了用戶承認(例如在彈出的是否信任的alert框中選擇always trust)。 kSecTrustResultUnspecified表示 serverTrust驗證成功,此證書也被暗中信任了,可是用戶並無顯示地決定信任該證書。 二者取其一就能夠認爲對serverTrust驗證成功。

SecTrustEvaluate

證書校驗函數,在函數的內部遞歸地從葉節點證書到根證書驗證。須要驗證證書自己的合法性(驗證簽名完整性,驗證證書有效期等);驗證證書頒發者的合法性(查找頒發者的證書並檢查其合法性,這個過程是遞歸的).而遞歸的終止條件是證書驗證過程當中遇到了錨點證書(錨點證書:嵌入到操做系統中的根證書,這個根證書是權威證書頒發機構頒發的自簽名證書)。

AFSecurityPolicy的源碼細節以下:

/**
 證書的驗證類型

 - AFSSLPinningModeNone: 不使用`pinned certificates`來驗證證書
 - AFSSLPinningModePublicKey: 使用`pinned certificates`來驗證證書的公鑰
 - AFSSLPinningModeCertificate: 使用`pinned certificates`來驗證整個證書
 */
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
    AFSSLPinningModeNone,
    AFSSLPinningModePublicKey,
    AFSSLPinningModeCertificate,
};

/**
 獲取指定證書的公鑰

 @param certificate 證書數據
 @return 公鑰
 */
static id AFPublicKeyForCertificate(NSData *certificate) {
    id allowedPublicKey = nil;
    SecCertificateRef allowedCertificate;
    SecPolicyRef policy = nil;
    SecTrustRef allowedTrust = nil;
    SecTrustResultType result;
    //獲取證書對象
    allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate);
    __Require_Quiet(allowedCertificate != NULL, _out);
    //獲取X.509的認證策略
    policy = SecPolicyCreateBasicX509();
    //獲取allowedTrust對象的值
    __Require_noErr_Quiet(SecTrustCreateWithCertificates(allowedCertificate, policy, &allowedTrust), _out);
    __Require_noErr_Quiet(SecTrustEvaluate(allowedTrust, &result), _out);
    //根據allowedTrust獲取對應的公鑰
    allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust);
//C++的gumpto跳轉,當前面的操做出錯之後,直接跳入_out執行
_out:
    if (allowedTrust) {
        CFRelease(allowedTrust);
    }
    if (policy) {
        CFRelease(policy);
    }
    if (allowedCertificate) {
        CFRelease(allowedCertificate);
    }
    //返回公鑰
    return allowedPublicKey;
}

/**
 在指定的證書和認證策略下,驗證SecTrustRef對象是不是受信任的、合法的。

 @param serverTrust SecTrustRef對象
 @return 結果
 */
static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {
    BOOL isValid = NO;
    SecTrustResultType result;
    //獲取serverTrust的認證結果,調用`SecTrustEvaluate`表示經過系統的證書來比較認證
    __Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out);
    isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);

_out:
    return isValid;
}

/**
 根據`serverTrust`獲取認證的證書鏈

 @param serverTrust serverTrust對象
 @return 認證證書鏈
 */
static NSArray * AFCertificateTrustChainForServerTrust(SecTrustRef serverTrust) {
    //獲取認證鏈的總層次
    CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust);
    NSMutableArray *trustChain = [NSMutableArray arrayWithCapacity:(NSUInteger)certificateCount];
    //獲取每一級認證鏈,把獲取的證書數據存入數組中
    for (CFIndex i = 0; i < certificateCount; i++) {
        SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i);
        [trustChain addObject:(__bridge_transfer NSData *)SecCertificateCopyData(certificate)];
    }
    //返回證書鏈數組
    return [NSArray arrayWithArray:trustChain];
}

/**
 獲取serverTrust對象的認證鏈的公鑰數組

 @param serverTrust serverTrust對象
 @return 公鑰數組
 */
static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) {
    //X.509標準的安全策略
    SecPolicyRef policy = SecPolicyCreateBasicX509();
    //獲取證書鏈的證書數量
    CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust);
    NSMutableArray *trustChain = [NSMutableArray arrayWithCapacity:(NSUInteger)certificateCount];
    for (CFIndex i = 0; i < certificateCount; i++) {
        SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i);

        SecCertificateRef someCertificates[] = {certificate};
        CFArrayRef certificates = CFArrayCreate(NULL, (const void **)someCertificates, 1, NULL);

        SecTrustRef trust;
        //經過一個證書、認證策略新建一個SecTrustRef對象
        __Require_noErr_Quiet(SecTrustCreateWithCertificates(certificates, policy, &trust), _out);
        
        SecTrustResultType result;
        //驗證SecTrustRef對象是否成功
        __Require_noErr_Quiet(SecTrustEvaluate(trust, &result), _out);
        //把SecTrustRef對應的公鑰加入數組中
        [trustChain addObject:(__bridge_transfer id)SecTrustCopyPublicKey(trust)];

    _out:
        if (trust) {
            CFRelease(trust);
        }

        if (certificates) {
            CFRelease(certificates);
        }

        continue;
    }
    CFRelease(policy);

    return [NSArray arrayWithArray:trustChain];
}

#pragma mark -

@interface AFSecurityPolicy()
//認證策略
@property (readwrite, nonatomic, assign) AFSSLPinningMode SSLPinningMode;
//公鑰集合
@property (readwrite, nonatomic, strong) NSSet *pinnedPublicKeys;
@end

@implementation AFSecurityPolicy
/**
 從MainBundle中獲取全部證書
 
 @param bundle 返回包含在bundle中的證書集合。若是AFNetworking使用的是靜態庫,咱們必須經過這個方法來加載證書。而且經過`policyWithPinningMode:withPinnedCertificates`方法來指定認證類型。
 @return 返回bundle裏面的證書
 */
+ (NSSet *)certificatesInBundle:(NSBundle *)bundle {
    //獲取項目裏的全部.cer證書
    NSArray *paths = [bundle pathsForResourcesOfType:@"cer" inDirectory:@"."];
    NSMutableSet *certificates = [NSMutableSet setWithCapacity:[paths count]];
    for (NSString *path in paths) {
        //獲取證書對應的NSData,而且加入集合中
        NSData *certificateData = [NSData dataWithContentsOfFile:path];
        [certificates addObject:certificateData];
    }
    //返回證書集合
    return [NSSet setWithSet:certificates];
}
/**
 返回當前類所在bundle所在的證書集合
 
 @return 證書集合
 */
+ (NSSet *)defaultPinnedCertificates {
    static NSSet *_defaultPinnedCertificates = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //獲取當前類所在bundle
        NSBundle *bundle = [NSBundle bundleForClass:[self class]];
        _defaultPinnedCertificates = [self certificatesInBundle:bundle];
    });

    return _defaultPinnedCertificates;
}
/**
 返回默認的安全認證策略,在這裏是驗證系統的證書。這個策略不容許非法證書、驗證主機名、不驗證證書內容和公鑰
 
 @return 返回認證策略
 */
+ (instancetype)defaultPolicy {
    AFSecurityPolicy *securityPolicy = [[self alloc] init];
    securityPolicy.SSLPinningMode = AFSSLPinningModeNone;

    return securityPolicy;
}

/**
 根據指定的認證策略和默認的證書列表初始化一個`AFSecurityPolicy`對象

 @param pinningMode 認證策略
 @return `AFSecurityPolicy`對象
 */
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode {
    return [self policyWithPinningMode:pinningMode withPinnedCertificates:[self defaultPinnedCertificates]];
}

/**
 經過制定的認證策略`pinningMode`和證書集合`pinnedCertificates`來初始化一個`AFSecurityPolicy`對象

 @param pinningMode 認證模型
 @param pinnedCertificates 證書集合
 @return AFSecurityPolicy對象
 */
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet *)pinnedCertificates {
    AFSecurityPolicy *securityPolicy = [[self alloc] init];
    securityPolicy.SSLPinningMode = pinningMode;
    //設置`_pinnedCertificates`和`pinnedPublicKeys`屬性,分別對應證書集合和公鑰集合
    [securityPolicy setPinnedCertificates:pinnedCertificates];
    //返回初始化成功的`AFSecurityPolicy`
    return securityPolicy;
}

- (instancetype)init {
    self = [super init];
    if (!self) {
        return nil;
    }
    //默認是要認證主機名稱
    self.validatesDomainName = YES;
    
    return self;
}

/**
經過指定的證書結合獲取到對應的公鑰集合。而後賦值給`pinnedPublicKeys`屬性
 @param pinnedCertificates 證書集合
 */
- (void)setPinnedCertificates:(NSSet *)pinnedCertificates {
    _pinnedCertificates = pinnedCertificates;

    if (self.pinnedCertificates) {
        NSMutableSet *mutablePinnedPublicKeys = [NSMutableSet setWithCapacity:[self.pinnedCertificates count]];
        //迭代每個證書
        for (NSData *certificate in self.pinnedCertificates) {
            //獲取證書對應的公鑰
            id publicKey = AFPublicKeyForCertificate(certificate);
            if (!publicKey) {
                continue;
            }
            [mutablePinnedPublicKeys addObject:publicKey];
        }
        //賦值給對應的屬性
        self.pinnedPublicKeys = [NSSet setWithSet:mutablePinnedPublicKeys];
    } else {
        self.pinnedPublicKeys = nil;
    }
}

#pragma mark -
/**
 爲serverTrust對象指定認證策略,若是domain不爲nil,則包括對主機名的認證。這個方法必須在接受到`authentication challenge`返回的時候調用。
 SecTrustRef能夠理解爲橋接證書與認證策略的對象,他關聯指定的證書與認證策略
 
 @param serverTrust 服務器的X.509標準的證書數據
 @param domain 認證服務器的主機名。若是是nil,則不會對主機名進行認證。
 @return serverTrust是否經過認證
 */
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
                  forDomain:(NSString *)domain
{
    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
        //  According to the docs, you should only trust your provided certs for evaluation.
        //  Pinned certificates are added to the trust. Without pinned certificates,
        //  there is nothing to evaluate against.
        //
        //  From Apple Docs:
        //          "Do not implicitly trust self-signed certificates as anchors (kSecTrustOptionImplicitAnchors).
        //           Instead, add your own (self-signed) CA certificate to the list of trusted anchors."
        NSLog(@"In order to validate a domain name for self signed certificates, you MUST use pinning.");
        return NO;
    }

    NSMutableArray *policies = [NSMutableArray array];
    if (self.validatesDomainName) {
        //使用須要認證主機名的認證策略
        [policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
    } else {
        //使用默認的認證策略
        [policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
    }
    //給serverTrust對象指定認證策略
    SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);

    if (self.SSLPinningMode == AFSSLPinningModeNone) {
        return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
    } else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
        return NO;
    }
    //根據證書驗證策略、數字簽名認證策略、其餘認證策略來處理不一樣狀況
    switch (self.SSLPinningMode) {
        case AFSSLPinningModeNone://不驗證公鑰和證書
        default:
            return NO;
        case AFSSLPinningModeCertificate: {//驗證整個證書
            NSMutableArray *pinnedCertificates = [NSMutableArray array];
            //根據指定證書獲取,獲取對應的證書對象
            for (NSData *certificateData in self.pinnedCertificates) {
                [pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
            }
            //把證書與serverTrust關聯起來
            SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);

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

            // obtain the chain after being validated, which *should* contain the pinned certificate in the last position (if it's the Root CA)
            //獲取serverTrust證書鏈。直到根證書。
            NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
            //若是`pinnedCertificates`包含`serverTrust`對象對應的證書鏈的根證書。則返回true
            for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
                if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
                    return YES;
                }
            }
            
            return NO;
        }
        case AFSSLPinningModePublicKey: {//只驗證證書裏面的數字簽名
            NSUInteger trustedPublicKeyCount = 0;
            //根據serverTrust對象和SecPolicyCreateBasicX509認證策略,獲取對應的公鑰集合
            NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);
            
            for (id trustChainPublicKey in publicKeys) {
                //把獲取的公鑰和系統獲取的默認公鑰比較,若是相等,則經過認證
                for (id pinnedPublicKey in self.pinnedPublicKeys) {
                    if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
                        trustedPublicKeyCount += 1;
                    }
                }
            }
            return trustedPublicKeyCount > 0;
        }
    }
    
    return NO;
}

#pragma mark - NSKeyValueObserving

+ (NSSet *)keyPathsForValuesAffectingPinnedPublicKeys {
    return [NSSet setWithObject:@"pinnedCertificates"];
}

#pragma mark - NSSecureCoding

+ (BOOL)supportsSecureCoding {
    return YES;
}

- (instancetype)initWithCoder:(NSCoder *)decoder {

    self = [self init];
    if (!self) {
        return nil;
    }

    self.SSLPinningMode = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(SSLPinningMode))] unsignedIntegerValue];
    self.allowInvalidCertificates = [decoder decodeBoolForKey:NSStringFromSelector(@selector(allowInvalidCertificates))];
    self.validatesDomainName = [decoder decodeBoolForKey:NSStringFromSelector(@selector(validatesDomainName))];
    self.pinnedCertificates = [decoder decodeObjectOfClass:[NSArray class] forKey:NSStringFromSelector(@selector(pinnedCertificates))];

    return self;
}

- (void)encodeWithCoder:(NSCoder *)coder {
    [coder encodeObject:[NSNumber numberWithUnsignedInteger:self.SSLPinningMode] forKey:NSStringFromSelector(@selector(SSLPinningMode))];
    [coder encodeBool:self.allowInvalidCertificates forKey:NSStringFromSelector(@selector(allowInvalidCertificates))];
    [coder encodeBool:self.validatesDomainName forKey:NSStringFromSelector(@selector(validatesDomainName))];
    [coder encodeObject:self.pinnedCertificates forKey:NSStringFromSelector(@selector(pinnedCertificates))];
}
#pragma mark - NSCopying
- (instancetype)copyWithZone:(NSZone *)zone {
    AFSecurityPolicy *securityPolicy = [[[self class] allocWithZone:zone] init];
    securityPolicy.SSLPinningMode = self.SSLPinningMode;
    securityPolicy.allowInvalidCertificates = self.allowInvalidCertificates;
    securityPolicy.validatesDomainName = self.validatesDomainName;
    securityPolicy.pinnedCertificates = [self.pinnedCertificates copyWithZone:zone];

    return securityPolicy;
}
@end

最後原文地址,demo地址

相關文章
相關標籤/搜索