最近公司项目的合作方陆续升级为https,本来是一件可喜可贺的事,但是乐极生悲,总有一些幺蛾子不那么让人省心...
ERROR:NSURLErrorDomain error code -999
一、报错信息解释
定义:NSURLErrorCancelled = -999
即:从定义很容易看出,该错误说明当前请求被取消了。
二、报错原因
为什么会被取消呢?
理解一个东西:
NSURLSessionAuthChallengeDisposition (如何处理证书)
NSURLSessionAuthChallengeUseCredential = 0, 使用该证书 安装该证书
NSURLSessionAuthChallengePerformDefaultHandling = 1, 默认采用的方式,该证书被忽略
NSURLSessionAuthChallengeCancelAuthenticationChallenge = 2, 取消请求,证书忽略
NSURLSessionAuthChallengeRejectProtectionSpace = 3, 拒绝
再看一段代码:
不难看出,如果evaluateServerTrust:forDomain方法返回NO,该请求就会被取消掉。下面是源码(已加注释):
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain
{
/*
1. allowInvalidCertificates:是否允许使用自建证书(服务器自己生成的CA证书)。
2. validatesDomainName:是否需要验证domain。如果你想验证自建证书的domain是否有效。那么你必须使用pinnedCertificates并且SSLPinningMode不为AFSSLPinningModeNone才可以。
3. SSLPinningMode:证书验证方式。
3.1. AFSSLPinningModeNone:表示不使用SSL pinning,无条件信任服务器证书。
3.2. AFSSLPinningModeCertificate:验证服务器返回证书和本地证书的所有部分。
3.3. AFSSLPinningModePublicKey :验证服务器返回证书和本地证书中的PublicKey部分。
4. 所以当客户端允许自建证书且需要验证域名时,没有导入的pinnedCertificates或者SSLPinningMode == AFSSLPinningModeNone,均表示无法验证该自建证书。所以都返回NO。
*/
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];
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;
}
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;
}
// 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;
}
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;
}
通过源码不难找出请求被取消的原因了。
三、 解决方法
- 方法一:对于一些没有自己SDK的合作第三方,目前部分是采用自建证书的方式,且更新至https是分阶段进行,所以不可能及时将证书拷贝给到用户,所以此时必须在项目中配置“允许自建”、“不需要验证域名”等,否则请求会被取消,出现题中错误。答案来源
manager.securityPolicy.allowInvalidCertificates = YES;
manager.securityPolicy.validatesDomainName = NO;
或者
manager.securityPolicy.allowInvalidCertificates = YES;
manager.securityPolicy.SSLPinningModep == AFSSLPinningModeNone;
- 方法二:
其余一些可能性
四、资料参考
图解SSL/TLS协议
SSL/TLS协议运行机制的概述
正确使用AFNetworking的SSL
AFNetworking源码解析