首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用自签名证书在iOS 9中进行HTTPS请求

使用自签名证书在iOS 9中进行HTTPS请求
EN

Stack Overflow用户
提问于 2015-09-15 10:08:48
回答 4查看 11.5K关注 0票数 10

我想用自签名证书向自定义服务器发出HTTPS请求.我使用NSURLConnection类和处理身份验证挑战,但总是在控制台中接收错误消息:

代码语言:javascript
复制
NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9802)

然后,使用以下错误调用"connection:didFailWithError:“方法:

代码语言:javascript
复制
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}

App接收两个身份验证挑战(NSURLAuthenticationMethodClientCertificate和NSURLAuthenticationMethodServerTrust),并以下列方式处理它们:

代码语言:javascript
复制
- (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”消息,并要求进行身份验证。在这里,应用程序试图发送身份验证响应,但立即收到错误。(同时,在服务器日志中,我根本没有看到任何关于握手的消息)。以下是诊断日志的一部分:

代码语言:javascript
复制
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文件中设置任何域异常。另外,app可以以IPv4形式通过IP地址请求服务器,但不能通过域名(如我的示例中所示)请求服务器。

我试过什么:

  • 用NSURLSession代替NSURLConnection,但没有成功;
  • 检查了苹果对服务器实现这里的ATS要求(后端开发人员确信他的实现满足了所有这些要求);
  • 根据堆栈溢出和苹果开发者论坛中解决的各种问题,使用锚证书进行信任验证;
  • 在开发者论坛上特别关注相似帖子及其相关的解决方案

我正在iPad Air 2上测试iOS 9 GM种子(Build 13A340)和xCode 7 GM Seed (Build 7A218)上的https请求。重要注意:这个功能在iOS 8中运行良好。考虑到这一点,我可以假设,这个问题在我们的服务器上,但是我们的后端开发人员向我保证,所有事情都很好。

现在我没有主意了。如果有人能给我一个提示,或者至少提出一些其他的诊断建议,这比“致命警报”更具体,我将不胜感激。

谢谢。

编辑1: SecTrustEvaluate总是返回kSecTrustResultRecoverableTrustFailure,这就是我必须找到某种解决办法的原因。

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2017-05-18 12:58:00

这个问题一段时间前就解决了。结果证明是无效的自签名证书。它并不能满足苹果公司的所有要求。不幸的是我不知道到底是什么。

票数 0
EN

Stack Overflow用户

发布于 2015-09-29 07:03:28

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

修复HTTP失败(kCFStreamErrorDomainSSL,-9802)的最佳方法是在info.plist文件中设置一个异常,如下所示:

代码语言:javascript
复制
<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那样安全。

票数 4
EN

Stack Overflow用户

发布于 2015-09-26 08:20:50

您是否使用nscurl来诊断连接问题?如果您的Mac操作系统运行OSXv10.11,您可以运行如下所示:

代码语言:javascript
复制
/usr/bin/nscurl --ats-diagnostics https://www.yourdomain.com

或者,如果您没有10.11,您可以在这里下载示例代码:https://developer.apple.com/library/mac/samplecode/SC1236/并使用XCode构建它,并按如下方式运行它(根据您的计算机需要更改路径):

代码语言:javascript
复制
/Users/somebody/Library/Developer/Xcode/DerivedData/TLSTool-hjuytnjaqebcfradighsrffxxyzq/Build/Products/Debug/TLSTool s_client -connect www.yourdomain.com:443

(要找到上述内容的完整路径,在构建完之后,打开中的Products组,右键单击TLSTool和"Show“。)

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

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/32583303

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档