AFNetworking知識點之AFSecurityPolicy

wjx-king· 2020-06-08

AFSecurityPolicy這個類是針對HTTPS連接時做的證書認證,這里我們假設你已經對HTTPS連接有了一定的了解。 也是比較簡單的一個類啊,看源碼吧

typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
    AFSSLPinningModeNone,
    AFSSLPinningModePublicKey,
    AFSSLPinningModeCertificate,
};

一個枚舉類型,定義了HTTPS的三種驗證模式: AFSSLPinningModeNone這個模式本地沒有保存證書,只驗證服務器證書是不是系統受信任證書列表里的證書簽發的。 AFSSLPinningModePublicKey這個模式表示本地存有證書,驗證證書的有效期等信息,也就是將本地證書設置為錨點證書,然后驗證證書有效性,再判斷本地證書和服務器證書是否一致。 AFSSLPinningModeCertificate這個模式同樣是本地存有證書,只是驗證時只驗證證書里的公鑰,不驗證證書的有效期等信息。

/**
  只讀屬性,證書驗證模式,只能在初始化的時候設置
 */
@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode;

/**
 本地證書的集合
 */
@property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates;

/**
 是否允許無效的證書,默認是NO
 */
@property (nonatomic, assign) BOOL allowInvalidCertificates;

/**
 是否驗證域名,默認是YES
 */
@property (nonatomic, assign) BOOL validatesDomainName;
/**
 返回指定目錄下的證書
*/
+ (NSSet <NSData *> *)certificatesInBundle:(NSBundle *)bundle;

/**
 默認驗證策略的初始化
 */
+ (instancetype)defaultPolicy;

/**
 根據制定的驗證模式初始化
 */
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode;

/**
 根據指定的驗證模式和本地證書初始化
 */
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet <NSData *> *)pinnedCertificates;

/**
 返回服務器證書是否能夠被信任,在NSUrlSessionDelegate的`URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler`方法中被調用
serverTrust 服務器證書驗證對象
domain      域名
 */
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
                  forDomain:(nullable NSString *)domain;

如果不制定驗證模式,就是在默認的AFSSLPinningModeNone驗證模式下驗證,所以不需要本地證書,但是如果要自己設置驗證模式,就可能需要本地證書,所以需要指定證書或者獲取工程下的所有證書。

#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

OSStatus SecItemExport(CFTypeRef secItemOrArray, SecExternalFormat outputFormat, SecItemImportExportFlags flags, const SecItemImportExportKeyParameters keyParams, CFDataRef _Nullable exportedData);這個函數是把secItemOrArray導出到exportedData。 __Require_noErr_Quiet(errorCode, exceptionLabel)就是當errocode不為0時 goto到exceptionLabel執行。

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
}

這個很簡單,比較兩個key是否相等,用于AFSSLPinningModePublicKey模式下比較公鑰。

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

    /**
     certificate轉換為SecCertificateRef類型的證書
     certificate 通過(__bridge CFDataRef)轉換成 CFDataRef
     allocator CFAllocator分配證書。
     data DER編碼的X.509證書。
     */
    allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate);
    //allowedCertificate如果是空跳轉到_out
    __Require_Quiet(allowedCertificate != NULL, _out);

    // 給allowedCertificates賦值
    allowedCertificates[0] = allowedCertificate;
    // 新建CF數組 tempCertificates
    tempCertificates = CFArrayCreate(NULL, (const void **)allowedCertificates, 1, NULL);

    //  新建policy為X.509
    policy = SecPolicyCreateBasicX509();
    // 根據證書、驗證策略、創建SecTrustRef對象賦值給allowedTrust,如果出錯就跳到_out標記處
    __Require_noErr_Quiet(SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust), _out);
    // 校驗證書,出錯跳轉到_out。
    __Require_noErr_Quiet(SecTrustEvaluate(allowedTrust, &result), _out);

    //copy出allowedTrust的公鑰
    allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust);

_out:
    ...

    return allowedPublicKey;
}

這個函數就是獲取證書文件的公鑰,需要了解的知識點都寫在注釋里了,看一下就可以了。

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

這個函數就是判斷SecTrustRef對象是否是有效的,SecTrustEvaluate(serverTrust, &result)這句就是驗證serverTrust,然后把驗證結果賦值給result。 kSecTrustResultUnspecified表示系統里有該證書的根證書,而且校驗通過。 kSecTrustResultProceed用戶設置了錨點證書,驗證通過。

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

這個函數是用來獲取服務器提供需要驗證的證書鏈。 SecTrustGetCertificateCount獲取SecTrustRef對象中的證書數量。 SecTrustGetCertificateAtIndex獲取SecTrustRef對象中的第index個證書。

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:
        ...
        continue;
    }
    CFRelease(policy);

    return [NSArray arrayWithArray:trustChain];
}

前面和獲取服務器驗證證書鏈的方法都一樣,就不說了。 CFArrayCreate(NULL, (const void **)someCertificates, 1, NULL);從someCertificates數組中copy 1個元素用于新創建的C數組中。 SecTrustCreateWithCertificates(certificates, policy, &trust)根據證書certificates和驗證策略policy創建一個SecTrustRef對象。 SecTrustCopyPublicKey (trust)獲取SecTrustRef對象中的publickey。

中間一些初始化、獲取本地證書的方法就不說了,直接看重點

- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
                  forDomain:(NSString *)domain
{
    //log里已經說得很清楚了,如果你要驗證自簽名證書的域名,那么就不能用AFSSLPinningModeNone模式,
    //而且必須copy服務器證書到本地
    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];
    // 如果需要驗證domain,那么就需要SecPolicyCreateSSL函數創建驗證策略,
    //server 參數為true表示驗證整個SSL證書鏈
    //hostname 傳入domain,用于判斷整個證書鏈上的domain是否和此處傳入domain一致
    if (self.validatesDomainName) {
        [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);

    // 如果SSLPinningMode為 AFSSLPinningModeNone,表示你不使用SSL pinning
    //使用AFServerTrustIsValid函數驗證serverTrust是否可信任,如果信任返回YES
    //判斷用戶是否信任自建證書,如果信任返回YES
    if (self.SSLPinningMode == AFSSLPinningModeNone) {
        return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
    }
    //如果不允許自建證書,serverTrust又不被信任,那么就表示驗證不通過返回NO
    else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
        return NO;
    }

    switch (self.SSLPinningMode) {
        case AFSSLPinningModeNone:
        default:
            return NO;
        //使用SSL Pinning方式驗證證書
        //要驗證整個證書一致才能通過
        case AFSSLPinningModeCertificate: {
            NSMutableArray *pinnedCertificates = [NSMutableArray array];
            for (NSData *certificateData in self.pinnedCertificates) {
                //SecCertificateCreateWithData函數把本地存儲的服務器證書拷貝數據轉換為DER編碼的 X.509證書
                //然后加入pinnedCertificates數組中
                [pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
            }
            // 將pinnedCertificates設置成需要參與驗證的Anchor Certificate(錨點證書,通過SecTrustSetAnchorCertificates設置了參與校驗錨點證書之后,假如驗證的數字證書是這個錨點證書的子節點,即驗證的數字證書是由錨點證書對應CA或子CA簽發的,或是該證書本身,則信任該證書),具體就是調用SecTrustEvaluate來驗證。
            SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);

            //判斷服務器證書是否是有效的(是否由上面設置的錨點證書或其子證書簽發,有效期等信息)
            if (!AFServerTrustIsValid(serverTrust)) {
                return NO;
            }

            // 獲取服務器需要驗證的證書鏈
            NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
            //驗證是否有本地證書與服務器返回的證書量一致的,如果有就返回YES
            for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
                if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
                    return YES;
                }
            }

            return NO;
        }
        //驗證公鑰(Publickey)一致就可以了
        case AFSSLPinningModePublicKey: {
            NSUInteger trustedPublicKeyCount = 0;

            //獲取服務器返回的證書的Publickey
            NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);

            // 依次遍歷本地Publickey和服務器Publickey,如果有一致的,那么就給trustedPublicKeyCount +1
            for (id trustChainPublicKey in publicKeys) {
                for (id pinnedPublicKey in self.pinnedPublicKeys) {
                    //用AFSecKeyIsEqualToKey函數驗證publickey
                    if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
                        trustedPublicKeyCount += 1;
                    }
                }
            }
            return trustedPublicKeyCount > 0;
        }
    }

    return NO;
}

需要解釋的都放在注釋里了

如有錯誤,請不吝賜教,謝謝!

实盘配资