本文讲解了如何使用AFNetworking实现HTTPS支持的不同方式,以及对AFNetworking实现源码分析。
Demo地址
实现默认方式支持HTTPS
首先恭喜你,无论你使用NSURLConnection,NSURLSession还是AFNetworking,默认已经支持了HTTPS,不用写任何代码。当然这也需要一些小的前提,服务端证书是有效证书,并且包含所请求的域名。但如果是自签名方式,则需要自己写代码实现。
实现自签名证书验证支持HTTPS
这里用12306网站来做测试,因为它是用的自签名证书。说个题外话,12306为什么用自签名证书?有兴趣的同学可以看下这里和这里。由于12306网站不支持Forward secrecy,所以需要在info.plist中设置NSExceptionRequiresForwardSecrecy
为NO
,具体设置参见Demo。
1、把下载的12306根证书导入项目,加载证书文件
1 2 3 4 5 6
| - (NSData *)loadCertificateData { NSString *cerName = @"SRCA"; NSString *cerPath = [[NSBundle mainBundle] pathForResource:cerName ofType:@"cer"]; NSData *certData = [NSData dataWithContentsOfFile:cerPath]; return certData; }
|
2、修改AFSecurityPolicy对象配置
1 2 3 4 5 6 7 8 9
| - (AFSecurityPolicy *)certificateSecurityPolicy { AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate]; securityPolicy.pinnedCertificates = @[[self loadCertificateData]]; securityPolicy.allowInvalidCertificates = YES; return securityPolicy; }
|
3、使用AFNetworking对象请求数据,链接地址为https://kyfw.12306.cn/otn/
1 2 3 4 5 6 7 8 9 10 11
| AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; manager.responseSerializer = [AFHTTPResponseSerializer serializer]; manager.securityPolicy = [self certificateSecurityPolicy]; NSString *httpsURL = @"https://kyfw.12306.cn/otn/"; [manager GET: httpsURL parameters:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { NSString *string = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding]; [self printLogWithTitle:@"AFNetworking Data:" content:string]; [self printLogWithTitle:@"AFNetworking Finish" content:@""]; } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { [self printLogWithTitle:@"TrustEvaluate Error" content:error]; }];
|
AFNetworking源码分析
在AFSecurityPolicy中有两个开关:
1 2 3 4 5
| @property (nonatomic, assign) BOOL allowInvalidCertificates; @property (nonatomic, assign) BOOL validatesDomainName;
|
还有三种验证模式:
1 2 3 4 5 6
| typedef NS_ENUM(NSUInteger, AFSSLPinningMode) { AFSSLPinningModeNone, AFSSLPinningModePublicKey, AFSSLPinningModeCertificate, };
|
我们可以利用他们来实现不同的验证策略,比如,实现自签名证书验证方式,allowInvalidCertificates
设为YES
,模式选择AFSSLPinningModeCertificate
,就可以了。
其中验证逻辑的核心方法是evaluateServerTrust
,上半部分为默认验证逻辑如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| NSMutableArray *policies = [NSMutableArray array]; if (self.validatesDomainName) { [policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)]; } else { [policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()]; } SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies); if (self.SSLPinningMode == AFSSLPinningModeNone) { return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust); } else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) { return NO; }
|
下半部分为不同模式下的验证逻辑,证书验证需要设置锚点证书(Anchor Certificate)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| 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)]; } SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates); if (!AFServerTrustIsValid(serverTrust)) { return NO; } NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust); for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) { 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; } }
|
AFServerTrustIsValid
中实际就是调用SecTrustEvaluate
进行验证评估
1 2 3 4 5 6 7 8 9 10
| static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) { BOOL isValid = NO; SecTrustResultType result; __Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out); isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed); _out: return isValid; }
|
在URLSession的代理中处理验证逻辑,具体实现细节
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| - (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); } }
|
总结整体流程
- 在Delegate中处理验证挑战
- 设置验证策略,锚点证书等
- 进行信任评估SecTrustEvaluate
- 生成凭证credential和disposition
- 完成或取消验证挑战
参考资料
- Making HTTP and HTTPS Requests
- HTTPS Server Trust Evaluation
- NSAppTransportSecurity
- iOS安全系列之一:HTTPS