美文网首页
AFNetworking - AFSecurityPolicy类

AFNetworking - AFSecurityPolicy类

作者: ASkyWatcher | 来源:发表于2020-06-07 21:22 被阅读0次

简单来说,AFSecurityPolicy的作用就是证书认证。

在https请求中,客户端会验证服务端的证书,但是这个过程也可能会受到中间人攻击,就像Charles或者Fiddler等工具抓包,为了防止受到它们的伪证书欺骗,这个时候证书锁定就有用了,将SSL证书内置到我们的应用里面,与服务端返回的证书进行校验。这也是AFSecurityPolicy的主要工作。

获取工程里面的.cer证书

+ (NSSet *)certificatesInBundle:(NSBundle *)bundle {
    NSArray *paths = [bundle pathsForResourcesOfType:@"cer" inDirectory:@"."];

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

实例化方法里面主要是设置了验证模式和证书
AFSSLPinningMode有三种模式

typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
    AFSSLPinningModeNone,//不进行本地验证
    AFSSLPinningModePublicKey,//只验证服务器返回的证书里的公钥
    AFSSLPinningModeCertificate,//验证本地证书和服务器返回证书
};

证书设置方法,并且将证书的公钥保存到pinnedPublicKeys

- (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
{
    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];
    //生成验证策略。如果要验证域名,就以域名为参数创建一个策略,否则创建默认的basicX509策略
    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);

    //如果是AFSSLPinningModeNone(不做本地证书验证,从客户端系统中的受信任颁发机构 CA 列表中去验证服务端返回的证书)
    if (self.SSLPinningMode == AFSSLPinningModeNone) {
        //不使用ssl pinning 但允许自建证书,直接返回YES;否则进行第二个条件判断,去客户端系统根证书里找是否有匹配的证书,验证serverTrust是否可信,直接返回YES
        return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
    } else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {        //如果验证无效AFServerTrustIsValid,而且allowInvalidCertificates不允许自签,返回NO
        return NO;
    }

    switch (self.SSLPinningMode) {
        case AFSSLPinningModeNone:
        default:
            return NO;
        case AFSSLPinningModeCertificate: {
            NSMutableArray *pinnedCertificates = [NSMutableArray array];
            for (NSData *certificateData in self.pinnedCertificates) {
                //把证书data,用系统api转成 SecCertificateRef 类型的数据,SecCertificateCreateWithData函数对原先的pinnedCertificates做一些处理,保证返回的证书都是DER编码的X.509证书

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

            // 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);
            
            for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
                //如果我们的证书中,有一个和它证书链中的证书匹配的,就返回YES
                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;
}

那么问题来了,

什么是SecTrustRef?

SecTrustRef:其实就是一个容器,装了服务器端需要验证的证书的基本信息、公钥等等,不仅如此,它还可以装一些评估策略,还有客户端的锚点证书,这个客户端的证书,可以用来和服务端的证书去匹配验证的。

那么什么又是锚点证书?

锚点证书通常指:嵌入到操作系统中的根证书(权威证书颁发机构颁发的自签名证书),CA 的证书链的锚点证书已经在系统中了,自签证书需要通过SecTrustSetAnchorCertificates方法设置为锚点证书,不然验证不会通过

X.509证书???

X.509是密码学里公钥证书的格式标准。X.509证书里含有公钥、身份信息和签名信息。HTTPS依赖的SSL证书使用的就是使用的X.509格式

相关文章

网友评论

      本文标题:AFNetworking - AFSecurityPolicy类

      本文链接:https://www.haomeiwen.com/subject/ofwctktx.html