2015-09-15 95 views
9

我想使用自签名证书向自定义服务器发出HTTPS请求。我使用NSURLConnection的类和加工认证的挑战,但总是在控制台收到错误消息:使用自签名证书在iOS 9中进行HTTPS请求

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

则方法“连接: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} 

应用接收两个验证质询(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诊断日志中,我可以看到握手过程即将开始。至少应用程序发送“ClientHello”消息,然后服务器发送其“ServerHello”消息并要求进行身份验证。这里的应用程序尝试发送身份验证响应,但立即收到错误。 (同时,在服务器日志中,我根本没有看到任何有关握手的消息)。这是诊断记录的一部分:

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的,但没有成功;
  • 检查了Apple的ATS服务器实施要求here(后端开发人员确信他的实施符合所有要求);
  • 根据stackoverflow和Apple开发人员论坛上解决的各种问题,为设置信任验证设置了锚定证书;
  • 特别注意similar帖子及其相关solution在开发者论坛;

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

现在我出来的想法。如果有人能够给我一个提示,或者至少提出一些其他的诊断方法,我会很感激,它会揭示特定的错误,比“致命的警报”更具体。

谢谢。

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

+0

您是否找到解决方案?我也有这个问题,我想使用我的本地服务器进行测试,但有了自签名证书,这是不可能的,我得到同样的错误... –

+0

还没有,我必须从事其他高优先级任务由于几个原因。如果我有解决方案,我会联系你。 – alfared

+0

这是一个iOS PITA协议错误。 (PITA代表....中的痛苦) – Josh

回答

0

这个问题是前一段时间解决的。原来是无效的自签名证书。它没有达到Apple的所有要求。不幸的是,我不知道,究竟是什么。

3

你使用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中建立并像这样运行它(更改路径适合您的机器):

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

(为了找到上述的完整路径,后你已经建好了,在你的Project Navigator中打开Products组,在TLSTool上点击右键,然后在Finder中显示。)

你已经链接到苹果在这个主题上的技术,https://developer.apple.com/library/prerelease/ios/technotes/App-Transport-Security-Technote/,但你没有说如果你是否运行nscurl。

+0

我的虚拟机端点通过/ usr/bin/nscurl中的所有测试--ats-diagnostics [https://mydomain.local:8888](https:// mydomain。本地:8888),但我仍然收到模拟器中的错误。 –

3

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

最好的办法来解决HTTP加载失败(kCFStreamErrorDomainSSL,-9802)如下设置例外的Info.plist文件:

<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安全。

+1

谢谢 - 关闭字典标签后工作得很好。 – dlw

+1

非常感谢,但是: 1.允许普通的HTTP下载,并且我需要HTTPS(出于安全原因)。 2.正如我写的,我不能在例外列表中设置任何域名,因为我们的客户通常在自己的服务器上安装后端。例如,它可以是“server.company1.com”和“server.company2.com”。您的答案假设我们必须重新构建应用程序,并在向第三方公司销售应用程序后添加新的例外域“server.company3.com”。 – alfared

+0

您可以使用ATS 上的Apple文档以及此博客文章中的信息组合在自签名证书#5上,特别是来解决您的问题(你可能需要迭代几次,直到你做对了)。 – spirographer

0

我只是ur's.Now遇到了同样的问题,我修复it.It是因为TLS版本和证书sign.As下面 apple's document

苹果的文件说,所以我做这个事情 info.plist setting

它工作