使用自签名证书在iOS 9中制作HTTPS请求

我想使用自签名证书向自定义服务器发出HTTPS请求。 我正在使用NSURLConnection类和处理身份validation的挑战,但始终在控制台中收到错误消息:

NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9802) 

那么方法“connection:didFailWithError:”被调用,出现以下错误:

 Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x150094100>, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, NSErrorPeerCertificateChainKey=<CFArray 0x1500ddd90 [0x19f6dab68]>{type = immutable, count = 1, values = ( 0 : <cert(0x14e6fb370) s: (server certificate name) i: (custom CA name)> )}, NSUnderlyingError=0x1504ae170 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSErrorFailingURLStringKey=https://217.92.80.156:9090/(method name and parameters), NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFNetworkCFStreamSSLErrorOriginalValue=-9802, kCFStreamPropertySSLPeerCertificates=<CFArray 0x1500ddd90 [0x19f6dab68]>{type = immutable, count = 1, values = ( 0 : <cert(0x14e6fb370) s: (server certificate name) i: (custom CA name)> )}, _kCFStreamPropertySSLClientCertificateState=2, kCFStreamPropertySSLPeerTrust=<SecTrustRef: 0x150094100>, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., _kCFStreamPropertySSLClientCertificates=<CFArray 0x14e5ee8e0 [0x19f6dab68]>{type = mutable-small, count = 2, values = ( 0 : <SecIdentityRef: 0x15012cd40> 1 : <cert(0x15014aa70) s: (client certificate name) i: (custom CA name)> )}, _kCFStreamErrorDomainKey=3, NSErrorFailingURLKey=https://217.92.80.156:9090/(method name and parameters), _kCFStreamErrorCodeKey=-9802}}, NSErrorClientCertificateChainKey=<CFArray 0x14e5ee8e0 [0x19f6dab68]>{type = mutable-small, count = 2, values = ( 0 : <SecIdentityRef: 0x15012cd40> 1 : <cert(0x15014aa70) s: (client certificate name) i: (custom CA name)> )}, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://217.92.80.156:9090/(method name and parameters), NSErrorFailingURLStringKey=https://217.92.80.156:9090/(method name and parameters), NSErrorClientCertificateStateKey=2} 

应用程序收到两个身份validation挑战(NSURLAuthenticationMethodClientCertificate和NSURLAuthenticationMethodServerTrust)并按以下方式处理它们:

 - (void) connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { if(challenge.proposedCredential && !challenge.error) { [challenge.sender useCredential:challenge.proposedCredential forAuthenticationChallenge:challenge]; return; } NSString *strAuthenticationMethod = challenge.protectionSpace.authenticationMethod; NSLog(@"authentication method: %@", strAuthenticationMethod); NSURLCredential *credential = nil; if([strAuthenticationMethod isEqualToString:NSURLAuthenticationMethodClientCertificate]) { // get identity and certificate from p.12 NSData *PKCS12Data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"client" ofType:@"p12"]]; NSDictionary *optionsDictionary = [NSDictionary dictionaryWithObject:@"password" forKey:(__bridge id)kSecImportExportPassphrase]; CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL); OSStatus securityError = SecPKCS12Import((__bridge CFDataRef)PKCS12Data,(__bridge CFDictionaryRef)optionsDictionary, &items); SecIdentityRef identity = NULL; SecCertificateRef certificate = NULL; if(securityError == errSecSuccess) { CFDictionaryRef myIdentityAndTrust = CFArrayGetValueAtIndex(items, 0); identity = (SecIdentityRef)CFDictionaryGetValue (myIdentityAndTrust, kSecImportItemIdentity); CFArrayRef array = (CFArrayRef)CFDictionaryGetValue(myIdentityAndTrust, kSecImportItemCertChain); certificate = (SecCertificateRef)CFArrayGetValueAtIndex(array, 0); } credential = [NSURLCredential credentialWithIdentity:identity certificates:[NSArray arrayWithObject:(__bridge id)(certificate)] persistence:NSURLCredentialPersistenceNone]; CFRelease(items); } else if([strAuthenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { int trustCertificateCount = (int)SecTrustGetCertificateCount(challenge.protectionSpace.serverTrust); NSMutableArray *trustCertificates = [[NSMutableArray alloc] initWithCapacity:trustCertificateCount]; for(int i = 0; i < trustCertificateCount; i ++) { SecCertificateRef trustCertificate = SecTrustGetCertificateAtIndex(challenge.protectionSpace.serverTrust, i); [trustCertificates addObject:(__bridge id) trustCertificate]; } SecPolicyRef policyRef = NULL; policyRef = SecPolicyCreateSSL(YES, (__bridge CFStringRef) challenge.protectionSpace.host); SecTrustRef trustRef = NULL; if(policyRef) { SecTrustCreateWithCertificates((__bridge CFArrayRef) trustCertificates, policyRef, &trustRef); CFRelease(policyRef); } if(trustRef) { // SecTrustSetAnchorCertificates(trustRef, (__bridge CFArrayRef) [NSArray array]); // SecTrustSetAnchorCertificatesOnly(trustRef, NO); SecTrustResultType result; OSStatus trustEvalStatus = SecTrustEvaluate(trustRef, &result); if(trustEvalStatus == errSecSuccess) { // just temporary attempt to make it working. // i hope, there is no such problem, when we have final working version of certificates. if(result == kSecTrustResultRecoverableTrustFailure) { CFDataRef errDataRef = SecTrustCopyExceptions(trustRef); SecTrustSetExceptions(trustRef, errDataRef); SecTrustEvaluate(trustRef, &result); } if(result == kSecTrustResultProceed || result == kSecTrustResultUnspecified) credential = [NSURLCredential credentialForTrust:trustRef]; } CFRelease(trustRef); } } else { DDLogWarn(@"Unexpected authentication method. Cancelling authentication ..."); [challenge.sender cancelAuthenticationChallenge:challenge]; } if(credential) [challenge.sender useCredential:credential forAuthenticationChallenge:challenge]; else [challenge.sender cancelAuthenticationChallenge:challenge]; } 

在CFNetwork诊断日志中,我可以看到Handshake程序即将开始。 至less应用程序发送“ClientHello”消息,然后服务器发送其“ServerHello”消息,并要求进行身份validation。 而这里的应用程序尝试发送身份validation响应,但立即收到错误。 (同时,在服务器日志中,我根本没有看到任何有关握手的消息)。 这里是诊断日志的一部分:

 Sep 15 10:51:49 AppName[331] <Notice>: CFNetwork Diagnostics [3:49] 10:51:49.185 { Authentication Challenge Loader: <CFURLRequest 0x1501931c0 [0x19f6dab68]> {url = https://217.92.80.156:9090/(method name and parameters), cs = 0x0} Challenge: challenge space https://217.92.80.156:9090/, ServerTrustEvaluationRequested (Hash f9810ad8165b3620) } [3:49] Sep 15 10:51:49 AppName[331] <Notice>: CFNetwork Diagnostics [3:50] 10:51:49.189 { Use Credential Loader: <CFURLRequest 0x1501931c0 [0x19f6dab68]> {url = https://217.92.80.156:9090/(method name and parameters), cs = 0x0} Credential: Name: server, Persistence: session } [3:50] Sep 15 10:51:49 AppName[331] <Notice>: CFNetwork Diagnostics [3:51] 10:51:49.190 { touchConnection Loader: <CFURLRequest 0x1501931c0 [0x19f6dab68]> {url = https://217.92.80.156:9090/(method name and parameters), cs = 0x0} Timeout Interval: 60.000 seconds } [3:51] Sep 15 10:51:49 AppName[331] <Notice>: CFNetwork Diagnostics [3:52] 10:51:49.192 { Response Error Request: <CFURLRequest 0x14e5d02a0 [0x19f6dab68]> {url = https://217.92.80.156:9090/(method name and parameters), cs = 0x0} Error: Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFNetworkCFStreamSSLErrorOriginalValue=-9802, kCFStreamPropertySSLPeerCertificates=<CFArray 0x1500ddd90 [0x19f6dab68]>{type = immutable, count = 1, values = ( 0 : <cert(0x14e6fb370) s: (server certificate name) i: (custom CA name)> )}, _kCFStreamPropertySSLClientCertificateState=2, kCFStreamPropertySSLPeerTrust=<SecTrustRef: 0x150094100>, _kCFStreamPropertySSLClientCertificates=<CFArray 0x14e5ee8e0 [0x19f6dab68]>{type = mutable-small, count = 2, values = ( 0 : <SecIdentityRef: 0x15012cd40> 1 : <cert(0x15014aa70) s: (client certificate name) i: (custom CA name)> )}, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802} } [3:52] 

我们的后端实例可以安装在客户端,所以我不能在Info.plist文件中设置任何域例外。 此外,应用程序可以通过IPv4格式的IP地址请求服务器,但不能通过域名(正如我的例子)。

我试过了什么:

  • 使用NSURLSession而不是NSURLConnection,但没有任何成功;
  • 在这里检查了苹果的ATS对服务器实现的要求(后端开发人员确信他的实现符合所有这些要求);
  • 根据stackoverflow和苹果开发者论坛上各种解决的问题,为设置信任validation设置了锚点证书;
  • 特别关注开发者论坛上的类似post及相关解决scheme ;

我使用iOS 9 GM种子(Build 13A340)和xCode 7 GM种子(Build 7A218)在iPad Air 2上testinghttps请求。 重要提示:此function在iOS 8下正常工作。考虑到这一点,我可能会认为,问题出在我们的服务器上,但是我们的后端开发人员向我保证,一切正常。

现在我没有想法了。 如果有人能够给我一个提示,或者至less提出一些其他的诊断方法,我将不胜感激,这将显示特定的错误,比“致命的警报”更具体。

谢谢。

编辑1:SecTrustEvaluate总是返回kSecTrustResultRecoverableTrustFailure,这就是为什么我不得不find某种解决方法。

你使用nscurl来诊断连接问题吗? 如果你有一台运行OS X v10.11的Mac,你可以运行如下的东西:

 /usr/bin/nscurl --ats-diagnostics https://www.yourdomain.com 

或者,如果你没有10.11,你可以在这里下载示例代码: https : //developer.apple.com/library/mac/samplecode/SC1236/并用XCode构build它,并像这样运行(改变path适合您的机器):

 /Users/somebody/Library/Developer/Xcode/DerivedData/TLSTool-hjuytnjaqebcfradighsrffxxyzq/Build/Products/Debug/TLSTool s_client -connect www.yourdomain.com:443 

(为了find上面的完整path,build好之后,打开Project Navigator中的Products组,右键点击TLSTool,然后点击“Show in Finder”。)

你已经链接到苹果的技术在这个问题上, https://developer.apple.com/library/prerelease/ios/technotes/App-Transport-Security-Technote/但你没有说如果你运行nscurl或不&#x3002;

根据这个: https : //forums.developer.apple.com/message/36842#36842

解决HTTP加载失败的最佳方法(kCFStreamErrorDomainSSL,-9802)是在info.plist文件中设置一个exception,如下所示:

 <key>NSAppTransportSecurity</key> <dict> <key>NSExceptionDomains</key> <dict> <key>test.testdomain.com</key> <dict> <key>NSIncludesSubdomains</key> <true/> <key>NSExceptionAllowsInsecureHTTPLoads</key> <true/> </dict> </dict> </dict> 

重要的一点是,这不是比iOS8更安全,只是不如iOS9支持的完整ATS安全。

我刚刚遇到同样的问题ur's.Now我修复它。这是因为tls版本和证书sign.As苹果的文件说下面的苹果文件

所以我做这个事情info.plist设置

它的工作

这个问题是前段时间解决的。 原来是无效的自签名证书。 它不符合苹果的所有要求。 不幸的是,我不知道,到底是什么。