AFSecurityPolicy

AFSSLPinningMode

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

设置了3中验证服务器是否受信任的方式

  • AFSSLPinningModeNone: 默认的认证方式,只会在系统的信任证书列表中对服务器返回的证书进行验证
  • AFSSLPinningModePublicKey:需要客户端预先保存服务器的证书
  • AFSSLPinningModeCertificate: 需要客户端事先保存服务器端发送的证书,但是只会验证证书中的公钥是否正确

AFSecurityPolicy初始化

+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet *)pinnedCertificates {
    AFSecurityPolicy *securityPolicy = [[self alloc] init];
    securityPolicy.SSLPinningMode = pinningMode;

    [securityPolicy setPinnedCertificates:pinnedCertificates];

    return securityPolicy;
}


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

初始化Policy
在调用PinnedCertificates的setter方法时,调用了AFPublicKeyForCertificateC函数,对证书进行操作获取公钥,取出全部的公钥保存到pinnedPublicKeys属性中

操作 SecTrustRef

SecTrustRef的操作都是C的API,定义在Security模块中

static id AFPublicKeyForCertificate(NSData *certificate) {
    //初始化临时变量
    id allowedPublicKey = nil;
    SecCertificateRef allowedCertificate;
    SecPolicyRef policy = nil;
    SecTrustRef allowedTrust = nil;
    SecTrustResultType result;
    //通过`DER`表示的数据生成一个`secCertificateRef`
    allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate);
    //判断返回值是否为空
    __Require_Quiet(allowedCertificate != NULL, _out);

    //创建一个默认的符合 X509 标准的 SecPolicyRef
    policy = SecPolicyCreateBasicX509();
    //通过默认的SecPolicyRef和证书创建一个SecTrustRef用于信任评估,对该对象进行信任评估,确认生成的 SecTrustRef 是值得信任的
    __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;
}

注意

每个SecTrustRef对象都包含多个SecCertificateRefSecPolicyRef。其中SecCertificateRef可以使用DER进行表示,并且其中存储着公钥信息

除此之外 还有操作还有AFCertificateTrustChainForServerTrustAFPublicKeyTrustChainForServerTrust函数
但是调用了几乎相同的API

  • SecTrustGetCertificateAtIndex 获取SecTrustRef中的证书
  • SecCertificateCopyData 从证书或者DER中表示的数据

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

验证服务端是否受信

通过[AFSecurityPolicy evaluateServerTrust:forDomain:]来验证服务器端是否受信

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

    #1: 不能隐式的信任自己签发的证书
    #2: 设置policy
    #3: 验证证书是否有效
    #4: 根据SSLPinningMode对服务端进行验证
    
    return NO;
}
  1. 不能隐式的信任自己签发的证书

    if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
    NSLog(@"In order to validate a domain name for self signed certificates, you MUST use pinning.");
    return NO;
    }

    因此如果没有提供证书或者不验证证书,并且还设置allowInvalidCertificates为真,满足上面所有条件 说明这次验证是不安全的 返回NO

  2. 设置Policy

    NSMutableArray *policies = [NSMutableArray array];
    if (self.validatesDomainName) {
    [policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
    } else {
    [policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
    }

    如果要验证域名的话,就以域名为参数创建一个SecPolicyRef,否则会创建一个符合X509标注的默认DecPlocyRef对象

  3. 验证证书有效性

    if (self.SSLPinningMode == AFSSLPinningModeNone) {
        return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
    } else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
    return NO;
    }
    • 如果只根据信任列表中证书进行验证,即self.SSLPinningMode == AFSSLPinningModeNone。如果允许无效的证书 就返回YES。不允许的话就对服务端进行验证
    • 如果服务器信任无效,并且不允许无效证书,就返回NO
  4. 根据SSLPingMode对服务器信任进行验证

    
    
    • AFSSLPinningModeNone直接返回NO
    • AFSSLPinningModeCertificate

      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's the Root CA)
      NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
      for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
      if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
      return YES;
      }
      }
      return NO;
      }

      a. 从self.pinnedCertificates中获取DER表示的数据
      b. 使用SecTrustSetAnchorCertigicates为服务器信任设置证书
      c. 判断服务器信任的有效性
      d. 使用AFCertificateTrustChainForServerTrust获取服务器信任中的全部DER表示的整数
      e. 如果pinnedCertificated中有相同的整数,就会返回YES

    • AFSSLPinningModePublicKey

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

      AFSSLPinningModeCertificate的不同点在于:

      • 从服务器信任中获取公钥
      • pinnedPublicKeys中的公钥与服务器信任中的公钥相同的属性大于0 返回真

与 AFURLSessionManager 协作

NSURLSessionDelegate代理方法中 调用运行这段代码

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

NSURLAuthenticationChallenge表示一个认证的挑战,提供了关于这次认证的全部信息。
其中的protectionSpace属性,保存了需要认证的保护空间,每个NSURLProtectionSpace对象都保存了主机地址、端口和认证方法等重要信息

如果保护空间的认证方法为NSURLAuthenticationMethodServerTrust,那么就会使用在上面提到的方法- [AFSecurityPolicy evaluateServerTrust:forDomain:] 对保护空间的serverTrust以及域名host进行认证

根据认证的结果,在completionHandler中传入不同的dispositioncredential参数

小结

AFSecurityPolicy 同样也作为一个即插即用的模块,在 AFNetworking 中作为验证 HTTPS 证书是否有效的模块存在