情况说明:用post方法加载一个h5页面,请求的url为https的,web服务器挂的证书是自建证书,报错:
2018-03-17 11:50:09.341674+0800 ICBCPayDemo[2216:1514635] TIC SSL Trust Error [7:0x1c01729c0]: 3:0
2018-03-17 11:50:09.542477+0800 ICBCPayDemo[2216:1514635] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813)
2018-03-17 11:50:09.543400+0800 ICBCPayDemo[2216:1514635] Task .<0> HTTP load failed (error code: -1202 [3:-9813])
2018-03-17 11:50:09.545843+0800 ICBCPayDemo[2216:1514343] NSURLConnection finished with error - code -1202
2018-03-17 11:50:09.554470+0800 ICBCPayDemo[2216:1514273] didFailLoadWithError:Error Domain=NSURLErrorDomain Code=-1202 "The certificate for this server is invalid. You might be connecting to a server that is pretending to be “ebankp-pay-agn.sdc.cs.icbc” which could put your confidential information at risk." UserInfo={NSURLErrorFailingURLPeerTrustErrorKey=, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9813, NSErrorPeerCertificateChainKey=
几种解决方式:
第一种:使用私有API,扩展NSURLRequest
只要填上这些就直接能用
第二种:生成.cer格式的证书,添加到工程中(未亲测)
方法来源:http://www.jb51.net/article/132096.html
1. 双击证书,这时证书已经添加到了钥匙串中
2. 将cer 文件拖入工程中
3. 如果使用的是AFNetwotking 的话,在代码中添加以下代码
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
//证书
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
manager.securityPolicy = securityPolicy;
// 2.设置证书模式
NSString * cerPath = [[NSBundle mainBundle] pathForResource:@"tomcat" ofType:@"cer"]; //tomcat是cer文件的名称
NSData * cerData = [NSData dataWithContentsOfFile:cerPath];
manager.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:[[NSSet alloc] initWithObjects:cerData, nil]];
// 客户端是否信任非法证书
manager.securityPolicy.allowInvalidCertificates = YES;
// 是否在证书域字段中验证域名
[manager.securityPolicy setValidatesDomainName:NO];
但这个cer文件不知道能不能导出来,没找到mac版chrome的导出方法,windows导出方式看:https://wdd.js.org/export-https-certificate-in-browser.html
这个导出方式看起来靠谱点儿,但也没试,感觉应该由环境配置人员去搞:http://www.cocoachina.com/ios/20160928/17663.html
第三:
自建证书,将证书导入工程(其实没有自建证书怎么导入工程啊哈)
- (void)viewDidLoad {
[superviewDidLoad];
//导入客户端证书
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"ca"ofType:@"cer"];
NSData *data = [NSData dataWithContentsOfFile:cerPath];
SecCertificateRef certificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef) data);
self.trustedCerArr = @[(__bridge_transfer id)certificate];
//发送请求
NSURL *testURL = [NSURL URLWithString:@"https://casetree.cn/web/test/demo.php"];
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionDataTask *task = [session dataTaskWithRequest:[NSURLRequest requestWithURL:testURL]];
[task resume];
// Do any additional setup after loading the view, typically from a nib.
}
#pragma mark - NSURLSessionDelegate
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler{
OSStatus err;
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
SecTrustResultType trustResult = kSecTrustResultInvalid;
NSURLCredential *credential = nil;
//获取服务器的trust object
SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
//将读取的证书设置为serverTrust的根证书
err = SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)self.trustedCerArr);
if(err == noErr){
//通过本地导入的证书来验证服务器的证书是否可信,如果将SecTrustSetAnchorCertificatesOnly设置为NO,则只要通过本地或者系统证书链任何一方认证就行
err = SecTrustEvaluate(serverTrust, &trustResult);
}
if(err == errSecSuccess && (trustResult == kSecTrustResultProceed || trustResult == kSecTrustResultUnspecified)){
//认证成功,则创建一个凭证返回给服务器
disposition = NSURLSessionAuthChallengeUseCredential;
credential = [NSURLCredential credentialForTrust:serverTrust];
}
else{
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
//回调凭证,传递给服务器
if(completionHandler){
completionHandler(disposition, credential);
}
}
不能解决问题的方式:
第一:
写进plist还是报错
相关知识点:
简单的来说,SSL/TSL通过四次握手,主要交换三个信息:
1. 数字证书:该证书包含了公钥等信息,一般是由服务器发给客户端,接收方通过验证这个证书是不是由信赖的CA签发,或者与本地的证书相对比,来判断证书是否可信;假如需要双向验证,则服务器和客户端都需要发送数字证书给对方验证;
2. 三个随机数
3. 加密通信协议
验证证书的API
相关的Api在Security Framework中,验证流程如下:
1). 第一步,先获取需要验证的信任对象(Trust Object)。这个Trust Object在不同的应用场景下获取的方式都不一样,对于NSURLConnection来说,是从delegate方法-connection:willSendRequestForAuthenticationChallenge:回调回来的参数challenge中获取([challenge.protectionSpace serverTrust])。
2). 使用系统默认验证方式验证Trust Object。SecTrustEvaluate会根据Trust Object的验证策略,一级一级往上,验证证书链上每一级数字签名的有效性(上一部分有讲解),从而评估证书的有效性。
3). 如第二步验证通过了,一般的安全要求下,就可以直接验证通过,进入到下一步:使用Trust Object生成一份凭证([NSURLCredential credentialForTrust:serverTrust]),传入challenge的sender中([challenge.sender useCredential:cred forAuthenticationChallenge:challenge])处理,建立连接。
4). 假如有更强的安全要求,可以继续对Trust Object进行更严格的验证。常用的方式是在本地导入证书,验证Trust Object与导入的证书是否匹配。更多的方法可以查看Enforcing Stricter Server Trust Evaluation,这一部分在讲解AFNetworking源码中会讲解到。
5). 假如验证失败,取消此次Challenge-Response Authentication验证流程,拒绝连接请求。
ps: 假如是自建证书的,则会跳过第二步,使用第三部进行验证,因为自建证书的根CA的数字签名未在操作系统的信任列表中。
搭建完HTTPS服务器之后,可以使用nscurl命令来进行检测,查看建立的HTTPS服务器是否能通过ATS特性。
1nscurl --ats-diagnostics --verbose https://casetree.cn
那么,如何本地导入证书进行验证呢?
在这里先提一下,由于iOS客户端支持的证书是DER格式的,我们需要创建客户端证书。创建客户端证书,直接将服务端的CA根证书导出成DER格式就行。
1openssl x509 -inform PEM -outform DER -inca.crt -out ca.cer
另外也有说:把服务器给你的自签证的证书放入bundle一般是.cer文件