AFNetworking 对HTTPS的处理

前言

        为了强制增强数据访问安全, iOS9 默认会把所有从NSURLConnection 、 CFURL 、 NSURLSession发出的 HTTP 请求,都改为 HTTPS 请求:iOS9.x-SDK编译时,默认会让所有从NSURLConnection 、 CFURL 、 NSURLSession发出的 HTTP 请求统一采用TLS 1.2 协议,这样子很多有网络请求的框架都会受到影响,例如AFNetworking2.0基于NSURLConnection, AFNetworking3.0基于NSURLSession,SDWebImage基于NSURLConnection等, 其实他们对于HTTPS的处理类似。本文主要讲AFNetworking3.0对于HTTPS的处理。

SSL Pinning

        可以理解为证书绑定,是指客户端直接保存服务端的证书,建立https连接时直接对比服务端返回的和客户端保存的两个证书是否一样,一样就表明证书是真的,不再去系统的信任证书机构里寻找验证。这适用于非浏览器应用,因为浏览器跟很多未知服务端打交道,无法把每个服务端的证书都保存到本地,但CS架构的像手机APP事先已经知道要进行通信的服务端,可以直接在客户端保存这个服务端的证书用于校验。

        为什么直接对比就能保证证书没问题?如果中间人从客户端取出证书,再伪装成服务端跟其他客户端通信,它发送给客户端的这个证书不就能通过验证吗?确实可以通过验证,但后续的流程走不下去,因为下一步客户端会用证书里的公钥加密,中间人没有这个证书的私钥就解不出内容,也就截获不到数据,这个证书的私钥只有真正的服务端有,中间人伪造证书主要伪造的是公钥。

        为什么要用SSL Pinning?正常的验证方式不够吗?如果服务端的证书是从受信任的的CA机构颁发的,验证是没问题的,但CA机构颁发证书比较昂贵,小企业或个人用户可能会选择自己颁发证书,这样就无法通过系统受信任的CA机构列表验证这个证书的真伪了,所以需要SSL Pinning这样的方式去验证。怎么创建自签名证书可以参考我之前写的一篇文章。

AFSecurityPolicy

在看AFSecurityPolicy源码之前我们先看看NSURLSession代理回调方法:

/*
 * 只要访问的是HTTPS的路径就会调用
 * 该方法的作用就是处理服务器返回的证书, 需要在该方法中告诉系统是否需要安装服务器返回的证书
 * NSURLAuthenticationChallenge : 授权质问
 * 受保护空间
 * 服务器返回的证书类型
 */
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    __block NSURLCredential *credential = nil;
    if (self.sessionDidReceiveAuthenticationChallenge) {
        disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
    } else {
        // 从服务器返回的受保护空间中拿到证书的类型
        // 判断服务器返回的证书是否是服务器信任的
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
            if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
                // 处理验证规则
                credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                if (credential) {
                    disposition = NSURLSessionAuthChallengeUseCredential;
                } else {
                    disposition = NSURLSessionAuthChallengePerformDefaultHandling;
                }
            } else {
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            }
        } else {
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        }
    }

    if (completionHandler) {
        // 安装证书
        completionHandler(disposition, credential);
    }
}

AFSecurityPolicy分三种验证模式:

  • AFSSLPinningModeNone:
            这个模式表示不做SSL pinning,只跟浏览器一样在系统的信任机构列表里验证服务端返回的证书。若证书是信任机构签发的就会通过,若是自己服务器生成的证书,这里是不会通过的。证书是否有效的标准是:信任链中如果只含有有效证书并且以可信锚点(trusted anchor)结尾,那么这个证书就被认为是有效的。其中可信锚点指的是系统隐式信任的证书,通常是包括在系统中的 CA 根证书。采用这种验证方式原理就是:系统对服务器返回来的证书链,从叶节点证书往根证书层层验证(有效期、签名等等),遇到根证书时,发现作为可信锚点的它存在与可信证书列表中,那么验证就通过,允许与服务端建立连接。

  • AFSSLPinningModeCertificate:
            这个模式表示用证书绑定方式验证证书,需要客户端保存有服务端的证书拷贝,这里验证分两步,第一步验证证书的域名有效期等信息,第二步是对比服务端返回的证书跟客户端返回的是否一致。

  • AFSSLPinningModePublicKey:
            这个模式同样是用证书绑定方式验证,客户端要有服务端的证书拷贝,
    只是验证时只验证证书里的公钥,不验证证书的有效期等信息。只要公钥是正确的,就能保证通信不会被窃听,因为中间人没有私钥,无法解开通过公钥加密的数据。

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

附上参考链接

你可能感兴趣的:(AFNetworking 对HTTPS的处理)