源码阅读:AFNetworking(四)——AFSecurityPolicy

1,979 阅读7分钟

该文章阅读的AFNetworking的版本为3.2.0。

AFSecurityPolicy这个类是用于验证HTTPS请求的证书是否正确。

1.接口

1.1.枚举

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

这个枚举定义了服务器证书验证模式:

AFSSLPinningModeNone 不使用证书验证服务器

AFSSLPinningModePublicKey 使用证书中的公钥验证服务器

AFSSLPinningModeCertificate 使用证书验证服务器

1.2.属性

/**
 服务器证书验证模式,默认是不验证
 */
@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode;

/**
 验证服务器的证书的集合,默认情况下,AFNetworking会搜索工程中所有.cer的证书文件,但不会将某个证书作为默认。如果想创建AFSecurityPolicy对象,就先调用certificatesInBundle方法加载证书,然后调用policyWithPinningMode:withPinnedCertificates方法创建对象
 */
@property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates;

/**
 是否信任无效或者过期的证书,默认为否
 */
@property (nonatomic, assign) BOOL allowInvalidCertificates;

/**
 是否验证证书中的域名,默认为是
 */
@property (nonatomic, assign) BOOL validatesDomainName;

1.3.方法

/**
 用这个方法查找包含在指定包中的所有的证书
 */
+ (NSSet <NSData *> *)certificatesInBundle:(NSBundle *)bundle;

/**
 实例化工厂方法,默认的配置包括不允许无效或者过期证书、验证域名、不验证证书和证书中公钥
 */
+ (instancetype)defaultPolicy;

/**
 指定证书验证模式的实例化工厂方法
 */
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode;

/**
 指定证书验证模式和证书的实例化工厂方法
 */
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet <NSData *> *)pinnedCertificates;

/**
 验证是否信任服务器
 */
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
                  forDomain:(nullable NSString *)domain;

2.实现

2.1.私有静态方法

这个方法是将key转为NSData类型

#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

这个方法是判断两个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
}

这个方法是用来从证书中取出公钥

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

    policy = SecPolicyCreateBasicX509();
    __Require_noErr_Quiet(SecTrustCreateWithCertificates(allowedCertificate, policy, &allowedTrust), _out);
    __Require_noErr_Quiet(SecTrustEvaluate(allowedTrust, &result), _out);

    allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust);

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

    if (policy) {
        CFRelease(policy);
    }

    if (allowedCertificate) {
        CFRelease(allowedCertificate);
    }

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

这个方法是用来获得服务器返回的所有的证书

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

这个方法是用来获得服务器返回的所有的证书中的公钥

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

这里面频繁使用了两个宏:__Require_Quiet__Require_noErr_Quiet,我们来看一下他们的定义:

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

可以看出__Require_Quiet这个宏的作用是当断言为假时,跳转并执行标记之后的代码

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

可以看出__Require_noErr_Quiet这个宏的作用是当断言为真时,跳转并执行标记之后的代码

2.2.类扩展

/**
 保存服务器证书验证模式
 */
@property (readwrite, nonatomic, assign) AFSSLPinningMode SSLPinningMode;

/**
 保存证书中的公钥的集合
 */
@property (readwrite, nonatomic, strong) NSSet *pinnedPublicKeys;

2.3.实现

+ (NSSet *)certificatesInBundle:(NSBundle *)bundle {
    // 获取包中所有后缀为.cer的文件路径
    NSArray *paths = [bundle pathsForResourcesOfType:@"cer" inDirectory:@"."];

    // 遍历文件路径并将其对应的文件转换为NSData类型数据
    NSMutableSet *certificates = [NSMutableSet setWithCapacity:[paths count]];
    for (NSString *path in paths) {
        NSData *certificateData = [NSData dataWithContentsOfFile:path];
        [certificates addObject:certificateData];
    }

    // 返回转换为二进制的证书的集合
    return [NSSet setWithSet:certificates];
}

+ (NSSet *)defaultPinnedCertificates {
    // 获取默认证书列表
    static NSSet *_defaultPinnedCertificates = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSBundle *bundle = [NSBundle bundleForClass:[self class]];
        _defaultPinnedCertificates = [self certificatesInBundle:bundle];
    });

    return _defaultPinnedCertificates;
}

+ (instancetype)defaultPolicy {
    // 按照默认实例化对象
    AFSecurityPolicy *securityPolicy = [[self alloc] init];
    securityPolicy.SSLPinningMode = AFSSLPinningModeNone;

    return securityPolicy;
}

+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode {
    // 调用全能实例化方法
    return [self policyWithPinningMode:pinningMode withPinnedCertificates:[self defaultPinnedCertificates]];
}

+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet *)pinnedCertificates {
    // 实例化对象并设置属性
    AFSecurityPolicy *securityPolicy = [[self alloc] init];
    securityPolicy.SSLPinningMode = pinningMode;

    [securityPolicy setPinnedCertificates:pinnedCertificates];

    return securityPolicy;
}

- (instancetype)init {
    self = [super init];
    if (!self) {
        return nil;
    }
    
    // 验证证书中的域名
    self.validatesDomainName = YES;

    return self;
}

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

- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
                  forDomain:(NSString *)domain
{
    // 当使用自建证书验证域名时,必须使用AFSSLPinningModePublicKey或者AFSSLPinningModeCertificate进行验证
    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设置验证策略,即告诉客户端如何验证serverTrust
    SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);

    // 如果验证策略为不验证
    if (self.SSLPinningMode == AFSSLPinningModeNone) {
        // 如果信任无效或者过期证书则直接通过验证,否则根绝验证证书情况返回
        return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
    // 如果验证策略为验证,但未验证通过并且不信任无效或者过期证书,返回未通过
    } else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
        return NO;
    }
    
    /**
     当代码走到这儿的时候代表:
     1.有验证策略
     2.证书验证通过
     3.不信任无效或者过期证书
     */

    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)];
            }
            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 is the Root CA)
            NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
            
            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;
}

2.4.KVO

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

添加依赖键,使pinnedPublicKeys属性依赖于pinnedCertificates属性,当pinnedCertificates属性值发生变化时,pinnedPublicKeys的观察者也能得到通知

2.5.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))];
}

关于NSSecureCodingAFNetworking源码阅读(二)——从AFURLRequestSerialization入手的"4.1.2.4.7 NSSecureCoding协议方法的实现"中已经解释过了,就不再复述

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

3.总结

因为对HTTP和SSL不是很了解,所以只做了一些大概功能的阅读。想详细了解的话还是得先认真学习HTTP和SSL相关知识。

源码阅读系列:AFNetworking

源码阅读:AFNetworking(一)——从使用入手

源码阅读:AFNetworking(二)——AFURLRequestSerialization

源码阅读:AFNetworking(三)——AFURLResponseSerialization

源码阅读:AFNetworking(四)——AFSecurityPolicy

源码阅读:AFNetworking(五)——AFNetworkReachabilityManager

源码阅读:AFNetworking(六)——AFURLSessionManager

源码阅读:AFNetworking(七)——AFHTTPSessionManager

源码阅读:AFNetworking(八)——AFAutoPurgingImageCache

源码阅读:AFNetworking(九)——AFImageDownloader

源码阅读:AFNetworking(十)——AFNetworkActivityIndicatorManager

源码阅读:AFNetworking(十一)——UIActivityIndicatorView+AFNetworking

源码阅读:AFNetworking(十二)——UIButton+AFNetworking

源码阅读:AFNetworking(十三)——UIImageView+AFNetworking

源码阅读:AFNetworking(十四)——UIProgressView+AFNetworking

源码阅读:AFNetworking(十五)——UIRefreshControl+AFNetworking

源码阅读:AFNetworking(十六)——UIWebView+AFNetworking