无法从iOS

时间:2015-10-05 04:21:36

标签: ios swift https tls1.2

我正在尝试访问https协议上提供的Web服务。最初我遇到了以下错误:

  

NSURLSession / NSURLConnection HTTP加载失败(kCFStreamErrorDomainSSL,-9802)   errorAn发生SSL错误,无法与服务器建立安全连接。

我通过在info.plist中添加以下内容来修复它:

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
    <key>NSExceptionDomains</key>
    <dict>
        <key>xx.xx.xxx.xxx</key>
        <dict>
            <key>NSExceptionAllowsInsecureHTTPLoads</key>
            <true/>
            <key>NSIncludesSubdomains</key>
            <true/>
            <key>NSThirdPartyExceptionAllowsInsecureHTTPLoads</key>
            <true/>
        </dict>
    </dict>
</dict>

但是现在我在connectionDidFinishLoading委托方法中得到以html响应:

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>400 Bad Request</title>
</head><body>
<h1>Bad Request</h1>
<p>Your browser sent a request that this server could not understand.<br />
</p>
</body>
</html>

我正在使用以下方式与服务器建立信任:

func connection(connection: NSURLConnection, canAuthenticateAgainstProtectionSpace protectionSpace: NSURLProtectionSpace) -> Bool{
    return true
}
func connection(connection: NSURLConnection, willSendRequestForAuthenticationChallenge challenge: NSURLAuthenticationChallenge){
    print("willSendRequestForAuthenticationChallenge")

    let protectionSpace:NSURLProtectionSpace = challenge.protectionSpace
    let sender: NSURLAuthenticationChallengeSender? = challenge.sender

    if(protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust){
        let trust:SecTrustRef = challenge.protectionSpace.serverTrust!
        let credential:NSURLCredential = NSURLCredential.init(forTrust: trust)
        sender?.useCredential(credential, forAuthenticationChallenge: challenge)
    }
    else{
        sender?.performDefaultHandlingForAuthenticationChallenge!(challenge)
    }
}

更新1

服务器日志显示以下错误:

  

通过SNI提供的主机名xx.xx.xxx.xx和通过HTTP提供的主机名my_secured_host_name是不同的

如何在SNI中添加主机名?

更新2

由于服务已经是https

,我已从info.plist传递http删除了密钥

更新3

当我尝试使用openssl时

  

openssl s_client -showcerts -connect xx.xx.xxx.xxx:443

但我收到以下错误:

  

CONNECTED(00000003)8012:错误:140790E5:SSL例程:SSL23_WRITE:ssl握手失败:/SourceCache/OpenSSL098/OpenSSL098-52.40.1/src/ssl/s23_lib.c:185

UPDATE4: 将Info.plist更改为以下内容:

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSExceptionDomains</key>
    <dict>
        <key>xx.xx.xxx.xxx</key>
        <dict>
            <key>NSExceptionRequiresForwardSecrecy</key>
            <false/>
            <key>NSIncludesSubdomains</key>
            <true/>
        </dict>
    </dict>
</dict>

仍然出现以下错误:

  

NSURLSession / NSURLConnection HTTP加载失败(kCFStreamErrorDomainSSL,-9802)   errorAn发生SSL错误,无法与服务器建立安全连接。

3 个答案:

答案 0 :(得分:2)

我和你有同样的问题,我可以解决它 - 主要是按照你已经研究过的,在这里发布的,以及Steven Peterson提供的背景信息。

我发现如果尝试使用以下设置进行连接,则-9802错误消失了:

NSAppTransportSecurity settings

然后只是看看哪些设置提供了解决方案。这只是一个问题,如果被删除,找出哪一个再打破它。

当然,这可能与您的情况不同。

请注意,我确实按照名称而不是数字来指定域名,正如之前magma所指出的那样。 另外(至少在我的情况下)没有编码来解决这个问题。

解决了这个问题之后,我后来了解到确定要使用哪些设置可以实现自动化:

/usr/bin/nscurl --ats-diagnostics --verbose https://your-domain.com

答案 1 :(得分:1)

如果您没有告诉我们真实的网址,那么很难帮助您

这些是用于安全连接的Apple要求:

  

这些是App Transport Security要求:服务器必须   至少支持传输层安全性(TLS)协议版本1.2。   连接密码仅限于提供前向保密的密码   (参见下面的密码列表。)

     

必须使用SHA256或更高签名哈希对证书进行签名   算法,使用2048位或更高的RSA密钥或256位或   更大的椭圆曲线(ECC)键。无效的证书会导致a   硬故障,没有联系。这些是公认的密码:

     
      
  • TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
  •   
  • TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
  •   
  • TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
  •   
  • TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
  •   
  • TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
  •   
  • TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
  •   
  • TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
  •   
  • TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
  •   
  • TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
  •   
  • TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
  •   
  • TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
  •   

所以,至少其中一个失败了

如果您不确定哪一个并且您有El Capitan,只需运行nscurl实用程序/usr/bin/nscurl --ats-diagnostics xx.xx.xxx.xxx,它将测试所有可能的键和值,并告诉您哪些可用。

我遇到了同样的问题以及对我有用的密钥(我的证书是用SHA1和SHA256或更高版本签名的):

<key>NSAppTransportSecurity</key>
    <dict>
        <key>NSExceptionDomains</key>
        <dict>
            <key>xx.xx.xxx.xxx</key>
            <dict>
                <key>NSExceptionRequiresForwardSecrecy</key>
                <false/>
                <key>NSIncludesSubdomains</key>
                <true/>
            </dict>
        </dict>
    </dict>

答案 2 :(得分:1)

除了已在其他答案和评论中解决的ATS问题之外,似乎您尝试通过其IP地址连接到SSL服务器。 以下参考可能有用(我将从Apple的iOS开发人员库中逐字引用):

  

覆盖主机名(允许一个特定站点的证书)   为另一个特定站点工作,或允许证书工作   当您通过IP地址连接到主机时,您必须更换   信任策略用于确定如何使用的策略对象   解释证书。为此,首先要创建一个新的TLS策略   所需主机名的对象。然后创建一个包含它的数组   政策。最后,告诉信任对象将来使用该数组   评估信任。

SecTrustRef changeHostForTrust(SecTrustRef trust)
{
        CFMutableArrayRef newTrustPolicies = CFArrayCreateMutable(
                kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);

        SecPolicyRef sslPolicy = SecPolicyCreateSSL(true, CFSTR("www.example.com"));

        CFArrayAppendValue(newTrustPolicies, sslPolicy);

#ifdef MAC_BACKWARDS_COMPATIBILITY
        /* This technique works in OS X (v10.5 and later) */

        SecTrustSetPolicies(trust, newTrustPolicies);
        CFRelease(oldTrustPolicies);

        return trust;
#else
        /* This technique works in iOS 2 and later, or
           OS X v10.7 and later */

        CFMutableArrayRef certificates = CFArrayCreateMutable(
                kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);

        /* Copy the certificates from the original trust object */
        CFIndex count = SecTrustGetCertificateCount(trust);
        CFIndex i=0;
        for (i = 0; i < count; i++) {
                SecCertificateRef item = SecTrustGetCertificateAtIndex(trust, i);
                CFArrayAppendValue(certificates, item);
        }

        /* Create a new trust object */
        SecTrustRef newtrust = NULL;
        if (SecTrustCreateWithCertificates(certificates, newTrustPolicies, &newtrust) != errSecSuccess) {
                /* Probably a good spot to log something. */

                return NULL;
        }

        return newtrust;
#endif
}

来源:iOS Developer Library — Overriding TLS Chain Validation Correctly — Manipulating Trust Objects

请注意,在同一页面上,您可以找到另一个处理自签名SSL证书的代码段,以防您处理此类证书。

要在Swift项目中使用此功能,请在项目中添加一个新的 C文件(File .. New .. File .. iOS / Source / C_File) ,例如mysectrust.c和相应的标头mysectrust.h如果XCode要求您创建桥接标头,请说是):

<强> mysectrust.h

#ifndef mysectrust_h
#define mysectrust_h

#include <Security/Security.h>

SecTrustRef changeHostForTrust(SecTrustRef trust);

#endif /* mysectrust_h */

<强> mysectrust.c

#include "mysectrust.h"

SecTrustRef changeHostForTrust(SecTrustRef trust)
{
    CFMutableArrayRef newTrustPolicies = CFArrayCreateMutable(
                                                              kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);

    SecPolicyRef sslPolicy = SecPolicyCreateSSL(true, CFSTR("www.example.com"));

    CFArrayAppendValue(newTrustPolicies, sslPolicy);

#ifdef MAC_BACKWARDS_COMPATIBILITY
    /* This technique works in OS X (v10.5 and later) */

    SecTrustSetPolicies(trust, newTrustPolicies);
    CFRelease(oldTrustPolicies);

    return trust;
#else
    /* This technique works in iOS 2 and later, or
     OS X v10.7 and later */

    CFMutableArrayRef certificates = CFArrayCreateMutable(
                                                          kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);

    /* Copy the certificates from the original trust object */
    CFIndex count = SecTrustGetCertificateCount(trust);
    CFIndex i=0;
    for (i = 0; i < count; i++) {
        SecCertificateRef item = SecTrustGetCertificateAtIndex(trust, i);
        CFArrayAppendValue(certificates, item);
    }

    /* Create a new trust object */
    SecTrustRef newtrust = NULL;
    if (SecTrustCreateWithCertificates(certificates, newTrustPolicies, &newtrust) != errSecSuccess) {
        /* Probably a good spot to log something. */

        return NULL;
    }

    return newtrust;
#endif
}

当然,请使用您的主机名替换上述代码中的www.example.com

然后,在Xcode项目projectname-Bridging-Header.h中找到桥接标题,并附加以下行:

#import "mysectrust.h"

现在你可以从Swift调用这个函数,例如:

func whatever(trust: SecTrustRef){

    let newTrust = changeHostForTrust(trust) // call to C function
    ...
}