AFNetworking源碼學(xué)習(xí)(二)- AFSecurityPolicy

AFSecurityPolicy是關(guān)于網(wǎng)絡(luò)連接安全方面的類

要想了解網(wǎng)絡(luò)安全方面的工作,首先得了解http/https通信,下面就分別先簡單介紹下http/https通信。

HTTP

HTTP(超文本傳輸協(xié)議)是一個(gè)客戶端和服務(wù)器請求和響應(yīng)的標(biāo)準(zhǔn),用于客戶端和服務(wù)器端之間的通信。

下圖為HTTP協(xié)議建立連接、通訊與關(guān)閉連接全過程

HTTP通信

HTTP是一套很簡單的通信協(xié)議,因此也非常的高效。但是由于通信數(shù)據(jù)都是明文發(fā)送的,很容易被攔截后造成破壞。在互聯(lián)網(wǎng)越來越發(fā)達(dá)的時(shí)代,對通信數(shù)據(jù)的安全要求也越來越高,所以HTTPS應(yīng)運(yùn)而生。

HTTPS

HTTPS(超文本傳輸安全協(xié)議)是以安全為目標(biāo)的HTTP通道,是一個(gè)通信安全的解決方案。

其實(shí)HTTPS就是身披SSL外殼的HTTP。

HTTP + 加密 + 認(rèn)證 + 完整性保護(hù) = HTTPS

通常HTTP直接和TCP通信,當(dāng)使用SSL就不同了。要先和SSL通信,再由SSL和TCP通信。

下圖就是HTTPS通信的全過程:

HTTPS通信

關(guān)于HTTPS通信更加有趣的解釋請參考:簡單粗暴系列之HTTPS原理
更多HTTPS通信細(xì)節(jié)可以參考:The First Few Milliseconds of an HTTPS Connection

加密

SSL(安全套接層)是為網(wǎng)絡(luò)通信提供安全及數(shù)據(jù)完整性的一種安全協(xié)議。SSL在傳輸層對網(wǎng)絡(luò)連接進(jìn)行加密。

常用加密算法分三大類:

  • 對稱加密算法(加解密密鑰相同):AES、DES、3DES
  • 非對稱算法(加密密鑰和解密密鑰不同):RSA、DSA、ECC
  • HASH算法:MD5、SHA-1

而網(wǎng)絡(luò)通信常用加密方法有兩種:

  • 共享密鑰加密:用的就是對稱加密算法,加密和解密通用一個(gè)密鑰。優(yōu)點(diǎn)是加密解密速度快,缺點(diǎn)是一旦密鑰泄露,別人也能解密數(shù)據(jù)。
  • 公開密鑰加密:能解決共享密鑰加密的困難。過程是:
    1. 發(fā)文方使用對方的公開密鑰進(jìn)行加密;
    2. 接受方在使用自己的私有密鑰進(jìn)行解密。

HTTP中沒有加密功能,但是可以通過和SSL組合使用,加密通信內(nèi)容,HTTP組合SSL也即HTTPS,HTTPS通信采用共享密鑰加密和公開密鑰加密兩者并用的混合加密機(jī)制:

  • 使用公開密鑰加密方式安全地交換在稍后的共享密鑰加密中要使用的密鑰;
  • 確保交換的密鑰是安全的前提下,使用共享密鑰加密方式進(jìn)行通訊。

關(guān)于非對稱加密可以參考這篇:RSA算法原理

AFSecurityPolicy.h

AFSecurityPolicy其實(shí)就是為了驗(yàn)證證書是否可信任

首先,是一個(gè)AFSSLPinningMode枚舉:

typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
    AFSSLPinningModeNone,        //代表無條件信任服務(wù)器的證書
    AFSSLPinningModePublicKey,   //代表會對服務(wù)器返回的證書中的PublicKey進(jìn)行驗(yàn)證
    AFSSLPinningModeCertificate, //代表會對服務(wù)器返回的證書同本地證書全部進(jìn)行校驗(yàn)
};

接下來聲明了四個(gè)屬性:

  • SSLPinningMode:返回SSL Pinning的類型。默認(rèn)的是AFSSLPinningModeNone。
  • pinnedCertificates:保存著所有的可用做校驗(yàn)的證書的集合。只要在證書集合中任何一個(gè)校驗(yàn)通過,evaluateServerTrust:forDomain: 就會返回true,即通過校驗(yàn)。
  • allowInvalidCertificates:使用允許無效或過期的證書,默認(rèn)是NO不允許。
  • validatesDomainName:是否驗(yàn)證證書中的域名domain,默認(rèn)是YES。

然后聲明了幾個(gè)方法:

+ (NSSet <NSData *> *)certificatesInBundle:(NSBundle *)bundle;

返回指定bundle中的證書。如果使用AFNetworking的證書驗(yàn)證 ,就必須實(shí)現(xiàn)此方法,并且使用policyWithPinningMode:withPinnedCertificates方法來創(chuàng)建security policy。

+ (instancetype)defaultPolicy;

默認(rèn)的security policy。其認(rèn)證設(shè)置為:不允許無效或過期的證書;驗(yàn)證domain域名;不對證書和公鑰進(jìn)行驗(yàn)證

+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode;
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet <NSData *> *)pinnedCertificates;

這兩個(gè)都是創(chuàng)建security policy的方法,只是初始化的參數(shù)不同。

最后一個(gè),是核心方法:

- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
                  forDomain:(nullable NSString *)domain;

第一個(gè)參數(shù):serverTrust是服務(wù)器發(fā)放的X.509信任證書;
第二個(gè)參數(shù):domain是發(fā)放信任證書的服務(wù)器域名。
這個(gè)方法是基于security policy來驗(yàn)證指定的服務(wù)器是否可信任。這個(gè)方法應(yīng)該在響應(yīng)服務(wù)器身份驗(yàn)證挑戰(zhàn)時(shí)使用。

AFURLSessionManagerURLSession:didReceiveChallenge:completionHandler:方法中有使用。

文件前面有提到過這樣一段話:

 `AFSecurityPolicy` evaluates server trust against pinned X.509 certificates and public keys over secure connections.

說的是AFSecurityPolicy 用來驗(yàn)證通過X.509(數(shù)字證書的標(biāo)準(zhǔn))的數(shù)字證書和公開密鑰進(jìn)行的安全網(wǎng)絡(luò)連接是否值得信任。這也就是上面方法需要做的事。

AFSecurityPolicy.m

文件最前面是幾個(gè)私有方法,可以看到很多方法里面都有以Sec為前綴的方法,這些都是<Security/Security.h>里的,我們可以通過查看Security的文檔了解相應(yīng)方法的功能。

#if !TARGET_OS_IOS && !TARGET_OS_WATCH && !TARGET_OS_TV
static NSData * AFSecKeyGetData(SecKeyRef key) {
    CFDataRef data = NULL;

    __Require_noErr_Quiet(SecItemExport(key, kSecFormatUnknown, kSecItemPemArmour, NULL, &data), _out);

    return (__bridge_transfer NSData *)data;

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

    return nil;
}
#endif

這個(gè)方法是把key導(dǎo)出為NSData,前提不是OS_IOS|OS_WATCH|OC_TV。核心為SecItemExport方法,其聲明為:

OSStatus SecItemExport(CFTypeRef secItemOrArray, SecExternalFormat outputFormat, SecItemImportExportFlags flags, const SecItemImportExportKeyParameters *keyParams, CFDataRef  _Nullable *exportedData);

對應(yīng)上面方法,key即為secItemOrArray,就是被導(dǎo)出的數(shù)組對象,而&data是exportedData,就是導(dǎo)出的對象。

導(dǎo)出的data轉(zhuǎn)化為NSData:

(__bridge_transfer NSData *)data

另外,需要注意__Require_noErr_Quiet這個(gè)方法,查看它的定義:

#ifndef __Require_noErr_Quiet
    #define __Require_noErr_Quiet(errorCode, exceptionLabel)                      \
      do                                                                          \
      {                                                                           \
          if ( __builtin_expect(0 != (errorCode), 0) )                            \
          {                                                                       \
              goto exceptionLabel;                                                \
          }                                                                       \
      } while ( 0 )
#endif

當(dāng)出現(xiàn)異常時(shí),執(zhí)行標(biāo)記以后的代碼,即_out:標(biāo)記下的代碼。

下面這個(gè)方法比較兩個(gè)key是否相等

static BOOL AFSecKeyIsEqualToKey(SecKeyRef key1, SecKeyRef key2) {
#if TARGET_OS_IOS || TARGET_OS_WATCH || TARGET_OS_TV
    return [(__bridge id)key1 isEqual:(__bridge id)key2];
#else
    return [AFSecKeyGetData(key1) isEqual:AFSecKeyGetData(key2)];
#endif
}

如果是OS_IOS|OS_WATCH|OC_TV,直接進(jìn)行比較;如果不是,需要先用之前的方法轉(zhuǎn)化為NSData,然后再進(jìn)行比較,因?yàn)?code>SecKeyRef是一個(gè)結(jié)構(gòu)體。

下面這個(gè)方法很長,但是也是很重要的,其作用是從證書中獲取公鑰

static id AFPublicKeyForCertificate(NSData *certificate) {
    id allowedPublicKey = nil;
    SecCertificateRef allowedCertificate;
    SecCertificateRef allowedCertificates[1];
    CFArrayRef tempCertificates = nil;
    SecPolicyRef policy = nil;
    SecTrustRef allowedTrust = nil;
    SecTrustResultType result;

//1.根據(jù)certificate生成SecCertificateRef類型證書allowedCertificate
    allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate);
    __Require_Quiet(allowedCertificate != NULL, _out);

//2.生成SecCertificateRef類型證書數(shù)組tempCertificates
    allowedCertificates[0] = allowedCertificate;
    tempCertificates = CFArrayCreate(NULL, (const void **)allowedCertificates, 1, NULL);

//3.新建policy為X.509
    policy = SecPolicyCreateBasicX509();
    
//4.生成SecTrustRef對象allowedTrust

__Require_noErr_Quiet(SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust), _out);

//5.校驗(yàn)allowedTrust,不是異步的
    __Require_noErr_Quiet(SecTrustEvaluate(allowedTrust, &result), _out);

//6.從allowedTrust中獲取PublicKey
    allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust);

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

    if (policy) {
        CFRelease(policy);
    }

    if (tempCertificates) {
        CFRelease(tempCertificates);
    }

    if (allowedCertificate) {
        CFRelease(allowedCertificate);
    }

    return allowedPublicKey;
}

上面注釋部分就是獲取公鑰的過程。帶Sec前綴的方法具體就不講了,在文檔中都可以查到。__Require_noErr_Quiet上面也介紹了,而__Require_Quiet其定義是:

#ifndef __Require_Quiet
    #define __Require_Quiet(assertion, exceptionLabel)                            \
      do                                                                          \
      {                                                                           \
          if ( __builtin_expect(!(assertion), 0) )                                \
          {                                                                       \
              goto exceptionLabel;                                                \
          }                                                                       \
      } while ( 0 )
#endif

其用途是:當(dāng)條件返回false時(shí),執(zhí)行標(biāo)記以后的代碼。那上面用到的地方就是:判斷allowedCertificate是否為空,如果為空,就執(zhí)行_out標(biāo)記下的代碼。

還有一個(gè)地方需要注意,為什么新建policy為SecPolicyCreateBasicX509()?,那是因?yàn)?strong>數(shù)字證書的格式都遵循X.509標(biāo)準(zhǔn)。

下面這個(gè)方法通過驗(yàn)證證書判斷服務(wù)器是否可信任

static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {
    BOOL isValid = NO;
    SecTrustResultType result;
    __Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out);

    isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);

_out:
    return isValid;
}

SecTrustEvaluate方法上面方法中也有使用過,其用途就是驗(yàn)證serverTrust證書是否可信任,其結(jié)果會賦值給result。而result是一個(gè)枚舉值,如果是下面兩種則表示證書是可信任的:

  • kSecTrustResultUnspecified:非用戶定義的,對應(yīng)驗(yàn)證失敗為kSecTrustResultRecoverableTrustFailure
  • kSecTrustResultProceed:用戶自定義的,對應(yīng)驗(yàn)證失敗為kSecTrustResultDeny

官方文檔HTTPS Server Trust EvaluationListing 3 Evaluating a trust object中就有提到這兩個(gè)值,有些注意點(diǎn)可以去看下。

下面方法是為了取出服務(wù)端返回的所有證書

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];
}

SecTrustRef其定義為:

typedef struct CF_BRIDGED_TYPE(id) __SecTrust *SecTrustRef;

文檔描述是:CFType used for performing X.509 certificate trust evaluations. (用于執(zhí)行X.509證書信任評估)。我們再看上面有關(guān)于SecTrustRef的創(chuàng)建:

__Require_noErr_Quiet(SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust), _out);

其本身是個(gè)結(jié)構(gòu)體,且通過SecCertificateRef類型證書數(shù)組和policy來創(chuàng)建,可知其至少包含這兩個(gè)。那上面方法中關(guān)于獲取所有證書的實(shí)現(xiàn)也就明了了。

最后一個(gè)私有方法,其目的是取出服務(wù)端返回證書里所有的public key

static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) {
    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;
        __Require_noErr_Quiet(SecTrustCreateWithCertificates(certificates, policy, &trust), _out);

        SecTrustResultType result;
        __Require_noErr_Quiet(SecTrustEvaluate(trust, &result), _out);

        [trustChain addObject:(__bridge_transfer id)SecTrustCopyPublicKey(trust)];

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

        if (certificates) {
            CFRelease(certificates);
        }

        continue;
    }
    CFRelease(policy);

    return [NSArray arrayWithArray:trustChain];
}

它就像是AFPublicKeyForCertificateAFCertificateTrustChainForServerTrust兩個(gè)方法的整合,具體就不講了,可以參考這兩個(gè)方法。

上面的私有方法中,很多地方用到了__bridge_transfer,它的作用是把CF對象類型轉(zhuǎn)換成OC對象類型,關(guān)于這種對象類型轉(zhuǎn)換還有兩種:

  • __bridge:CF和OC對象轉(zhuǎn)化時(shí)只涉及對象類型不涉及對象所有權(quán)的轉(zhuǎn)化;
  • __bridge_retained:常用在將OC對象轉(zhuǎn)換成CF對象時(shí),將OC對象的所有權(quán)交給CF對象來管理;(作用同CFBridgingRetain())
  • __bridge_transfer:常用在將CF對象轉(zhuǎn)換成OC對象時(shí),將CF對象的所有權(quán)交給OC對象,此時(shí)ARC就能自動(dòng)管理該內(nèi)存;(作用同CFBridgingRelease())

接下來就是@implementation部分了,先介紹前面幾個(gè)類方法:

  • certificatesInBundle::獲取制定目錄下的所有證書,是以NSData的形式;
  • defaultPinnedCertificates:獲取默認(rèn)目錄下的所有證書;
  • defaultPolicy:創(chuàng)建默認(rèn)Policy,即SSLPinningMode為AFSSLPinningModeNone;
  • policyWithPinningMode::創(chuàng)建Policy,并設(shè)置SSLPinningMode;
  • policyWithPinningMode:withPinnedCertificates::創(chuàng)建Policy,并設(shè)置pinningMode和pinnedCertificates。

接下來分析該文件的核心方法:

- (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) {
    //如果要驗(yàn)證域名,就通過域名來生成Policy
        [policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
    } else {
    //不驗(yàn)證域名,就默認(rèn)基于X.509
        [policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
    }
    
    //設(shè)置policies
    SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);

    if (self.SSLPinningMode == AFSSLPinningModeNone) {
    //不校驗(yàn)證書,只要允許過期無效證書或者serverTrust驗(yàn)證通過,即可信任
        return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
    } else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
    //否則,就不可信任
        return NO;
    }

//到了這里就說明:
//1.通過了證書的驗(yàn)證
//2.allowInvalidCertificates = YES

    switch (self.SSLPinningMode) {
        case AFSSLPinningModeNone:
        default:
            return NO;
        case AFSSLPinningModeCertificate: {
            //驗(yàn)證全部證書
            NSMutableArray *pinnedCertificates = [NSMutableArray array];
            for (NSData *certificateData in self.pinnedCertificates) {
                [pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
            }
            SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);

            //驗(yàn)證證書是否可信任
            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)
            NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
            
            //整個(gè)證書鏈都跟本地的證書匹配才通過驗(yàn)證
            for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
                if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
                    return YES;
                }
            }
            
            return NO;
        }
        case AFSSLPinningModePublicKey: {
            NSUInteger trustedPublicKeyCount = 0;
            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;
}

至于后面幾個(gè)方法就不多講了,主要關(guān)于kvo和自定義對象歸檔。

總結(jié)

關(guān)于網(wǎng)絡(luò)安全方面的知識還有很多,AFSecurityPolicy類是其中一個(gè)重要的點(diǎn),主要是為了驗(yàn)證證書是否可信任。

學(xué)習(xí)到的知識點(diǎn):

  • HTTP、HTTPS通信過程
  • 加密的相關(guān)知識
  • Security框架相關(guān)API
  • __bridge_transfer把CF對象轉(zhuǎn)換成OC對象
  • __Require_noErr_Quiet
  • __Require_Quiet
  • 數(shù)字證書的格式都遵循X.509標(biāo)準(zhǔn)
  • 自定義對象歸檔
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容