菜单

AFNetwork处理https自建证书网络请求原理

2019年9月12日 - 理财婆高手论坛

前言

最近项目里一部分接口用到了服务端的自建证书,
之前一直是用AFNetwork来处理相关的逻辑,
第一次接触到https证书认证相关逻辑, 写篇小笔记记录下

https在iOS的具体体现

在整个https通讯流程中, 对于app来说, 关键点其实就是公钥的认证.
确保当前通讯的服务端所下发的公钥与公司所在host的服务端是同一个证书所导出的公钥,
才开始使用该公钥来加密传输数据.

AFNetworking要使用自建证书的代码逻辑

先上处理代码, 要使用自建证书时, 把证书的数据放入AFHTTPSessionManager单例

//cerPath为证书在bundle里的路径NSData *certData = [NSData dataWithContentsOfFile:cerPath];SecCertificateRef httpBinCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef));NSSet *certSet = [[NSSet alloc] initWithObjects:(__bridge_transfer NSData *)SecCertificateCopyData(httpBinCertificate), nil]; AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey];// 是否允许,NO-- 不允许无效的证书[securityPolicy setAllowInvalidCertificates:NO];// 设置证书[securityPolicy setPinnedCertificates:certSet];[AFHTTPSessionManager manager].securityPolicy = securityPolicy;

核心方法

如果是用的自建证书是无法通过系统的默认认证规则的,
这时https连接就会被中断, 如果要令自建证书也可以建立https连接,
就需要实现以下这个代理方法

- URLSession:(NSURLSession *)sessiondidReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler

如果你响应这个方法,
系统会将本次TCP链接中的证书数据通过这个代理方法中的challenge参数抛给你,
当你验证完成后, 通过调用completionHandler回调告知系统你的验证结果

AFNetworking处理自建证书的逻辑

- URLSession:(NSURLSession *)sessiondidReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(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); }}

这里的关键是你验证证书后通过completionHandler告知系统你的验证结果,
该block回调第一个参数就是你的验证结果

typedef NS_ENUM(NSInteger, NSURLSessionAuthChallengeDisposition) { NSURLSessionAuthChallengeUseCredential = 0, /* Use the specified credential, which may be nil */ NSURLSessionAuthChallengePerformDefaultHandling = 1, /* Default handling for the challenge - as if this delegate were not implemented; the credential parameter is ignored. */ NSURLSessionAuthChallengeCancelAuthenticationChallenge = 2, /* The entire request will be canceled; the credential parameter is ignored. */ NSURLSessionAuthChallengeRejectProtectionSpace = 3, /* This challenge is rejected and the next authentication protection space should be tried; the credential parameter is ignored. */} 

这些结果枚举基本能望文生义就不多做解释了,
completionHandler第二个参数一般就是把challenge里的证书对象回传进去,
这一步的关键是如何验证这个证书对象是合法的, 在afn中,
验证的逻辑放在这个方法里

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

AFNetworking处理https验证的几种方式

证书里面包含里公钥, SHA-256指纹等多种信息, 根据实际需要,
验证策略可以有所不同, 下面

二进制数据比对
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]) { //直接比对NSData是否一致 if ([self.pinnedCertificates containsObject:trustChainCertificate]) { return YES; } } return NO;}

最简单粗暴的方式, 直接将证书转成NSData进行比对,
完全一致就可以确定是同一张证书

公钥比对
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;}

只比对证书的公钥, 毕竟只要公钥没被修改, 即使传输了数据,
对方也没有对应的私钥可以解密你的数据, 相对没那么严谨的验证方式,
由于证书链里会有个多证书,
注意afn里面的逻辑是主要证书链上有任意一个节点的证书公钥与self.pinnedPublicKeys这个受信任的公钥是相同时,
就会认为这次认证是成功的

疑问

看完之后还是有几点小疑问

第一是如果证书里面带有该证书的SHA-256签名,
那是不是等于不需要在客户端预埋证书呢? 只需要写死SHA-256的值,
比对服务端下发的证书的SHA-256是否一致就可以提取公钥了.

第二个是锚点证书的问题, 在afn里如果使用自建证书,
afn会帮你将该自建证书设置为锚点证书.
但其实注释了锚点证书的设置也一样能建立起https连接,
那设置锚点证书的意义在哪呢?

待续…

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图