16

我正在尝试访问可在 https 协议上使用的 Web 服务。最初我收到以下错误:

NSURLSession/NSURLConnection HTTP 加载失败 (kCFStreamErrorDomainSSL, -9802) 错误发生 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 委托方法中得到以下响应:

<!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

我已经从 info.plist 中删除了通过 http 的密钥,因为该服务已经是 https

更新 3

当我尝试使用 openssl 作为

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

但我收到以下错误:

CONNECTED(00000003) 8012:error: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) 错误发生 SSL 错误,无法与服务器建立安全连接。

4

3 回答 3

2

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

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

NSAppTransportSecurity 设置

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

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

请注意,正如之前岩浆指出的那样,我确实按名称而不是按编号指定域。此外(至少在我的情况下)解决这个问题没有涉及任何编码。

解决了这个问题后,我后来了解到可以自动确定要使用的设置:

/usr/bin/nscurl --ats-diagnostics --verbose https://your-domain.com
于 2015-10-07T07:48:45.897 回答
1

如果您不告诉我们真实的 URL,将很难为您提供帮助

以下是 Apple 对安全连接的要求:

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

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

  • 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>
于 2015-10-07T06:25:14.190 回答
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 开发者库 — 正确覆盖 TLS 链验证 — 操作信任对象

请注意,在同一页面上,您可以找到另一个处理自签名 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
    ...
}   
于 2015-10-07T07:09:17.910 回答