AICon 上海站|90%日程已就绪,解锁Al未来! 了解详情
写点什么

验证 HTTPS 请求的证书(五)

  • 2019-12-10
  • 本文字数:6568 字

    阅读完需:约 22 分钟

验证 HTTPS 请求的证书(五)

关注仓库,及时获得更新:iOS-Source-Code-Analyze


自 iOS9 发布之后,由于新特性 App Transport Security 的引入,在默认行为下是不能发送 HTTP 请求的。很多网站都在转用 HTTPS,而 AFNetworking 中的 AFSecurityPolicy 就是为了阻止中间人攻击,以及其它漏洞的工具。


AFSecurityPolicy 主要作用就是验证 HTTPS 请求的证书是否有效,如果 app 中有一些敏感信息或者涉及交易信息,一定要使用 HTTPS 来保证交易或者用户信息的安全。

AFSSLPinningMode

使用 AFSecurityPolicy 时,总共有三种验证服务器是否被信任的方式:


Objective-C


typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {    AFSSLPinningModeNone,    AFSSLPinningModePublicKey,    AFSSLPinningModeCertificate,};
复制代码


  • AFSSLPinningModeNone 是默认的认证方式,只会在系统的信任的证书列表中对服务端返回的证书进行验证

  • AFSSLPinningModeCertificate 需要客户端预先保存服务端的证书

  • AFSSLPinningModeCertificate 也需要预先保存服务端发送的证书,但是这里只会验证证书中的公钥是否正确

初始化以及设置

在使用 AFSecurityPolicy 验证服务端是否受到信任之前,要对其进行初始化,使用初始化方法时,主要目的是设置验证服务器是否受信任的方式


Objective-C


+ (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;}
复制代码


这里没有什么地方值得解释的。不过在调用 pinnedCertificate 的 setter 方法时,会从全部的证书中取出公钥保存到 pinnedPublicKeys 属性中。


Objective-C


- (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; }}
复制代码


在这里调用了 AFPublicKeyForCertificate 对证书进行操作,返回一个公钥。

操作 SecTrustRef

serverTrust 的操作的函数基本上都是 C 的 API,都定义在 Security 模块中,先来分析一下在上一节中 AFPublicKeyForCertificate 的实现


Objective-C


static id AFPublicKeyForCertificate(NSData *certificate) {    id allowedPublicKey = nil;    SecCertificateRef allowedCertificate;    SecCertificateRef allowedCertificates[1];    CFArrayRef tempCertificates = nil;    SecPolicyRef policy = nil;    SecTrustRef allowedTrust = nil;    SecTrustResultType result;
allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate); __Require_Quiet(allowedCertificate != NULL, _out);
allowedCertificates[0] = allowedCertificate; tempCertificates = CFArrayCreate(NULL, (const void **)allowedCertificates, 1, NULL);
policy = SecPolicyCreateBasicX509(); __Require_noErr_Quiet(SecTrustCreateWithCertificates(tempCertificates, 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 (tempCertificates) { CFRelease(tempCertificates); }
if (allowedCertificate) { CFRelease(allowedCertificate); }
return allowedPublicKey;}
复制代码


  1. 初始化一坨临时变量


   id allowedPublicKey = nil;    SecCertificateRef allowedCertificate;    SecCertificateRef allowedCertificates[1];    CFArrayRef tempCertificates = nil;    SecPolicyRef policy = nil;    SecTrustRef allowedTrust = nil;    SecTrustResultType result;
复制代码


  1. 使用 SecCertificateCreateWithData 通过 DER 表示的数据生成一个 SecCertificateRef,然后判断返回值是否为 NULL


   allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate);    __Require_Quiet(allowedCertificate != NULL, _out);
复制代码


* 这里使用了一个非常神奇的宏 `__Require_Quiet`,它会判断 `allowedCertificate != NULL` 是否成立,如果 `allowedCertificate` 为空就会跳到 `_out` 标签处继续执行
复制代码


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


  1. 通过上面的 allowedCertificate 创建一个 CFArray


   allowedCertificates[0] = allowedCertificate;    tempCertificates = CFArrayCreate(NULL, (const void **)allowedCertificates, 1, NULL);
复制代码


* 下面的 `SecTrustCreateWithCertificates` 只会接收数组作为参数。
复制代码


  1. 创建一个默认的符合 X509 标准的 SecPolicyRef,通过默认的 SecPolicyRef 和证书创建一个 SecTrustRef 用于信任评估,对该对象进行信任评估,确认生成的 SecTrustRef 是值得信任的。


   policy = SecPolicyCreateBasicX509();    __Require_noErr_Quiet(SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust), _out);    __Require_noErr_Quiet(SecTrustEvaluate(allowedTrust, &result), _out);
复制代码


* 这里使用的 `__Require_noErr_Quiet` 和上面的宏差不多,只是会根据返回值判断是否存在错误。
复制代码


  1. 获取公钥


   allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust);
复制代码


* 这里的 `__bridge_transfer` 会将结果桥接成 `NSObject` 对象,然后将 `SecTrustCopyPublicKey` 返回的指针释放。
复制代码


  1. 释放各种 C 语言指针


   if (allowedTrust) {        CFRelease(allowedTrust);    }
if (policy) { CFRelease(policy); }
if (tempCertificates) { CFRelease(tempCertificates); }
if (allowedCertificate) { CFRelease(allowedCertificate); }
复制代码


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


对它的操作还有 AFCertificateTrustChainForServerTrustAFPublicKeyTrustChainForServerTrust 但是它们几乎调用了相同的 API。


Objective-C


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];}
复制代码


  • SecTrustGetCertificateAtIndex 获取 SecTrustRef 中的证书

  • SecCertificateCopyData 从证书中或者 DER 表示的数据

验证服务端是否受信

验证服务端是否守信是通过 - [AFSecurityPolicy evaluateServerTrust:forDomain:] 方法进行的。


Objective-C


- (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;    }
复制代码


> Do not implicitly trust self-signed certificates as anchors (kSecTrustOptionImplicitAnchors).> Instead, add your own (self-signed) CA certificate to the list of trusted anchors.* 所以如果没有提供证书或者不验证证书,并且还设置 `allowInvalidCertificates` 为**真**,满足上面的所有条件,说明这次的验证是不安全的,会直接返回 `NO`
复制代码


  1. 设置 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 标准的默认 `SecPolicyRef` 对象
复制代码


  1. 验证证书的有效性


   if (self.SSLPinningMode == AFSSLPinningModeNone) {        return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);    } else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {        return NO;    }
复制代码


* 如果**只根据信任列表中的证书**进行验证,即 `self.SSLPinningMode == AFSSLPinningModeNone`。如果允许无效的证书的就会直接返回 `YES`。不允许就会对服务端信任进行验证。* 如果服务器信任无效,并且不允许无效证书,就会返回 `NO`
复制代码


  1. 根据 SSLPinningMode 对服务器信任进行验证


   switch (self.SSLPinningMode) {        case AFSSLPinningModeNone:        default:            return NO;        case AFSSLPinningModeCertificate: {            ...        }        case AFSSLPinningModePublicKey: {            ...        }    }
复制代码


* `AFSSLPinningModeNone` 直接返回 `NO`* `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;
复制代码


    1.  从 `self.pinnedCertificates` 中获取 DER 表示的数据    2.  使用 `SecTrustSetAnchorCertificates` 为服务器信任设置证书    3.  判断服务器信任的有效性    4.  使用 `AFCertificateTrustChainForServerTrust` 获取服务器信任中的全部 DER 表示的证书    5.  如果 `pinnedCertificates` 中有相同的证书,就会返回 `YES`
* `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;
复制代码


    * 这部分的实现和上面的差不多,区别有两点
1. 会从服务器信任中获取公钥 2. `pinnedPublicKeys` 中的公钥与服务器信任中的公钥相同的数量大于 0,就会返回真
复制代码

与 AFURLSessionManager 协作

在代理协议 - URLSession:didReceiveChallenge:completionHandler: 或者 - URLSession:task:didReceiveChallenge:completionHandler: 代理方法被调用时会运行这段代码


Objective-C


if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {    if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {        disposition = NSURLSessionAuthChallengeUseCredential;        credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];    } else {        disposition = NSURLSessionAuthChallengeRejectProtectionSpace;    }} else {    disposition = NSURLSessionAuthChallengePerformDefaultHandling;}
复制代码


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


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


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

小结

  • AFSecurityPolicy 同样也作为一个即插即用的模块,在 AFNetworking 中作为验证 HTTPS 证书是否有效的模块存在,在 iOS 对 HTTPS 日渐重视的今天,在我看来,使用 HTTPS 会成为今后 API 开发的标配。

相关文章

关于其他 AFNetworking 源代码分析的其他文章:



本文转载自 Draveness 技术博客。


原文链接:https://draveness.me/afnetworking5


2019-12-10 17:471398

评论

发布
暂无评论
发现更多内容

数字赋能制造:赛力斯汽车的零代码创新实践

明道云

Elasticsearch vs 向量数据库:寻找最佳混合检索方案

Zilliz

全文检索 elasticsearch 向量检索 Milvus 混合检索

探索1688商品详情API:轻松解锁商品信息的全面视角

代码忍者

API 接口 pinduoduo API

GPUStack v0.4:文生图模型、语音模型、推理引擎版本管理、离线支持和部署本地模型

SEAL安全

Stable Diffusion LLM Whisper Speech-to-Text Text-to-Speech

数字藏品NFT的合约开发

北京木奇移动技术有限公司

软件外包公司 音乐NFT 体育NFT

喜报!极限科技(INFINI Labs)通过国家高新技术企业认定

极限实验室

科技 极限科技 高新企业认定

大数据平台Bug Bash大扫除最佳实践

京东科技开发者

GPUStack v0.4:文生图模型、语音模型、推理引擎版本管理、离线支持和部署本地模型

GPUStack

Stable Diffusion LLM Whisper Speech-to-Text Text-to-Speech

LlamaIndex工作流可视化的AI助手分析探索

代码忍者

API 接口 pinduoduo API

制造业数字化演进历程中的创新与HAP 赋能

明道云

火山引擎数据飞轮最新活动:结合大模型能力,探索金融行业数智化落地新可能

字节跳动数据平台

数据飞轮

七牛云荣获「2024 鸿蒙生态 SDK 星河奖」

七牛云

从MySQL JOIN 算法角度看如何优化SQL

京东科技开发者

信创背景下医院信息化建设的挑战与机遇

明道云

RFID技术在ERP系统中的集成应用

积木链小链

ERP RFID

企业数字化转型的矛与盾

明道云

让零代码系统界面体验更优秀

明道云

收藏品NFT的开发流程

北京木奇移动技术有限公司

NFT数字藏品系统 NFT开发 软件外包公司

集团企业分级管理授权能力建设

芯盾时代

iam 权限管理系统 授权访问

最新前端架构设计:中央仓库管理-基于工作空间和git-submodule实现共用和管理

京东科技开发者

OpenAI o1 模型到来后,谈谈提示词工程的未来

Baihai IDP

程序员 AI Prompt LLMs 提示词工程

更轻更省!DataSimba敏捷版直播火热预约中

奇点云

验证 HTTPS 请求的证书(五)_文化 & 方法_Draveness_InfoQ精选文章