iOS三方框架之 - AFNetworking的Https認證流程

對AFNetworking進行解耦 AFNetworking解耦後能夠分爲如下幾個模塊: 1. NSURLSession:主要的一個基於NSURLSession的管理模塊; 2. Reachability:網絡監測模塊; 3. Security:Https驗證模塊; 4. Serialization:序列化模塊,包含了請求和響應的序列化; 5. UIKit:包含了一些UI的擴展,方便調用。api

1.jpg

NSURLSessionReachability 還有 SerializationUIKit 這些模塊就不作過多的解析了,重點了解一下 Security中Https的認證。瀏覽器

Security

  • Https通信流程 詳見之前的一篇文章:https通信流程,方便起見,主流程我截了張圖:
    image.png

裏面有關鍵的一步:即客戶端驗證服務端返回的公鑰證書,這就是https的驗證:安全

- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
複製代碼

只要是Https的請求,就會走到這個方法,該方法的做用就是處理服務器返回的證書, 須要在該方法中告訴系統是否須要安裝服務器返回的證書,安裝即表明公鑰證書驗證經過。bash

咱們來經過AFNetworking的源碼來查看一下它是怎麼處理的,源碼裏有詳細註釋:服務器

- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    //NSURLAuthenticationChallenge 須要處理的挑戰,裏面包含了一個類型爲 NSURLProtectionSpace 的屬性
    //NSURLProtectionSpace裏面包含了 host port  authenticationMethod等屬性,還有服務器用於https驗證的一些信息好比公鑰、random_s等,這些都是服務器傳過來的,authenticationMethod表示受權方式,對於AFNetworking來講,是經過比較NSURLAuthenticationMethodServerTrust來決定是否須要證書驗證


    /* 處理方式
     NSURLSessionAuthChallengeUseCredential:使用指定證書
    NSURLSessionAuthChallengePerformDefaultHandling:默認方式處理
    NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消挑戰
    NSURLSessionAuthChallengeRejectProtectionSpace:拒絕挑戰,並嘗試下一個驗證保護空間,忽略證書參數
     */
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;


    /* NSURLCredential爲一個證書,它決定了咱們如何處理這個挑戰,它的初始化方法有:
     
     //通常用於須要處理身份驗證401的錯誤驗證
     - (instancetype)initWithUser:(NSString *)user password:(NSString *)password persistence:(NSURLCredentialPersistence)persistence
     
     //經過鑰匙串中取得一個客戶端證書來建立證書,通常用於服務端進行雙向認證
     + (NSURLCredential *)credentialWithIdentity:(SecIdentityRef)identity certificates:(nullable NSArray *)certArray persistence:(NSURLCredentialPersistence)persistence
     
    //經過服務端傳回的SecTrustRef(包含待驗證的證書和支持的驗證方法等)對象建立證書,通常客戶端須要驗證服務端身份時使用,即Https中的客戶端對服務端證書驗證這一步,收到服務端的challenge,例如https須要驗證證書等 ats開啓
     - (instancetype)initWithTrust:(SecTrustRef)trust
     */
    __block NSURLCredential *credential = nil;

    if (self.sessionDidReceiveAuthenticationChallenge) {
        //若是須要自定義處理當前的挑戰,那麼就經過block回調回去
        disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
    } else {
        //挑戰憑證不必定都是進行HTTPS證書的信任,也多是須要客戶端提供用戶密碼或者提供雙向驗證時的客戶端證書。當這個挑戰憑證被驗證經過時,請求即可以繼續順利進行
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
            //開始驗證證書 ,只須要驗證服務端證書是否安全(即https的單向認證,這是AF默認處理的認證方,其餘的認證方式,只能由咱們自定義Block的實現
            if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
                //驗證成功後,經過該證書建立一個NSURLCredential ,該證書會被傳給服務端
                credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                //肯定挑戰方式
                if (credential) {
                    //證書挑戰
                    disposition = NSURLSessionAuthChallengeUseCredential;
                } else {
                    //默認挑戰
                    disposition = NSURLSessionAuthChallengePerformDefaultHandling;
                }
            } else {
                 //驗證失敗,代表客戶端沒辦法處理,那麼就Cancel掉
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            }
        } else {
            //默認挑戰
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        }
    }

    if (completionHandler) {
        //將驗證完成的證書發給服務器
        completionHandler(disposition, credential);
    }
}
複製代碼

下面跳入真正作驗證的方法: 先來認識幾個屬性:網絡

  • SSLPinningMode :返回SSL Pinning的類型。默認的是AFSSLPinningModeNone。
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
    AFSSLPinningModeNone,        //表明無條件信任服務器的證書,這個模式表示不作SSL pinning,只跟瀏覽器同樣在系統的信任機構列表裏驗證服務端返回的證書。若證書是信任機構簽發的就會經過,如果本身服務器生成的證書,這裏是不會經過的。
    AFSSLPinningModePublicKey,   //表明會對服務器返回的證書中的PublicKey進行驗證,客戶端要有服務端的證書拷貝,驗證時只驗證證書裏的公鑰,不驗證證書的有效期等信息。只要公鑰是正確的,就能保證通訊不會被竊聽,由於中間人沒有私鑰,沒法解開經過公鑰加密的數據。
    AFSSLPinningModeCertificate, //表明會對服務器返回的證書同本地證書所有進行校驗,須要客戶端保存有服務端的證書拷貝,這裏驗證分兩步,第一步驗證證書的域名/有效期等信息,第二步是對比服務端返回的證書跟客戶端返回的是否一致。
};
複製代碼
  • pinnedCertificates:保存着全部的可用作校驗的證書的集合。只要在證書集合中任何一個校驗經過,evaluateServerTrust:forDomain: 就會返回true,即經過校驗。
  • allowInvalidCertificates:使用容許無效或過時的證書,默認是NO不容許。
  • validatesDomainName:是否驗證證書中的域名domain,默認是YES。

上一步生成證書的方法爲:session

credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
//serverTrust爲SecTrustRef類型的數據,就是待驗證的對象。
複製代碼
  • SecTrustRef:其實就是一個容器,裏面裝了服務器須要驗證證書的基本信息、公鑰等等,不只如此,它還能夠裝一些評估策略,還有客戶端的錨點證書,這個客戶端的證書,能夠用來和服務端證書去匹配驗證。 每個SecTrustRef對象包含多個SecCertificateRef 和 SecPolicyRef。其中 SecCertificateRef 可使用 DER 進行表示。
  • domain:服務器域名,用於域名驗證
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
                  forDomain:(NSString *)domain
{
    //若是有域名 設置了容許信任無效或過時證書  須要驗證域名  沒有提供證書或者不驗證證書  返回NO
    if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
        return NO;
    }
    //用來裝驗證策略
    NSMutableArray *policies = [NSMutableArray array];

    //生成驗證策略。若是要驗證域名,就以域名爲參數建立一個策略,不然建立默認的basicX509策略
    if (self.validatesDomainName) {
        // 若是須要驗證domain,那麼就使用SecPolicyCreateSSL函數建立驗證策略,其中第一個參數爲true表示爲服務器證書驗證建立一個策略,第二個參數傳入domain,匹配主機名和證書上的主機名
        //1.__bridge:CF和OC對象轉化時只涉及對象類型不涉及對象全部權的轉化
        //2.__bridge_transfer:經常使用在講CF對象轉換成OC對象時,將CF對象的全部權交給OC對象,此時ARC就能自動管理該內存
        //3.__bridge_retained:(與__bridge_transfer相反)經常使用在將OC對象轉換成CF對象時,將OC對象的全部權交給CF對象來管理
        [policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
    } else {
        // 若是不須要驗證domain,就使用默認的BasicX509驗證策略
        [policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
    }
    // 爲serverTrust設置驗證策略
    SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);

    if (self.SSLPinningMode == AFSSLPinningModeNone) {
        //不作SSL pinning,只在系統的信任機構列表裏驗證服務端返回的證書
        return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
    } else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
        //若是驗證無效AFServerTrustIsValid,並且allowInvalidCertificates不容許自籤,返回NO
        return NO;
    }

    switch (self.SSLPinningMode) {
        case AFSSLPinningModeNone:
        default:  //這裏上面已經判斷過了,執行到這裏的話直接返回NO
            return NO;
        //客戶端把服務端證書拷貝,存放在self.pinnedCertificates中
        case AFSSLPinningModeCertificate: {
            //所有校驗(nsbundle .cer)
            NSMutableArray *pinnedCertificates = [NSMutableArray array];
            //把證書data,用系統api轉成 SecCertificateRef 類型的數據,SecCertificateCreateWithData函數對原先的pinnedCertificates作一些處理,保證返回的證書都是DER編碼的X.509證書
            for (NSData *certificateData in self.pinnedCertificates) {
                [pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
            }

            // 將pinnedCertificates設置成須要參與驗證的Anchor Certificate(錨點證書,經過SecTrustSetAnchorCertificates設置了參與校驗錨點證書以後,假如驗證的數字證書是這個錨點證書的子節點,即驗證的數字證書是由錨點證書對應CA或子CA簽發的,或是該證書自己,則信任該證書),具體就是調用SecTrustEvaluate來驗證
            //serverTrust是服務器來的驗證,有須要被驗證的證書
            // 把本地證書設置爲根證書,
            SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);

            //再去驗證證書
            if (!AFServerTrustIsValid(serverTrust)) {
                return NO;
            }

            //注意,這個方法和咱們以前的錨點證書不要緊了,是去從咱們須要被驗證的服務端證書,去拿證書鏈。
            // 服務器端的證書鏈,注意此處返回的證書鏈順序是從葉節點到根節點
            // 全部服務器返回的證書信息
            NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
            
            for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
                //若是咱們的證書中,有一個和它證書鏈中的證書匹配的,就返回YES
                // 是否本地包含相同的data
                if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
                    return YES;
                }
            }
            
            return NO;
        }
        case AFSSLPinningModePublicKey: {
            //只驗證公鑰
            NSUInteger trustedPublicKeyCount = 0;
            // 從serverTrust中取出服務器端傳過來的全部可用的證書,並依次獲得相應的公鑰
            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
                        trustedPublicKeyCount += 1;
                    }
                }
            }
            return trustedPublicKeyCount > 0;
        }
    }
    
    return NO;
}
複製代碼
相關文章
相關標籤/搜索