【问题标题】:Unable to access https web service from iOS无法从 iOS 访问 https 网络服务
【发布时间】:2016-01-01 16:12:07
【问题描述】:

我正在尝试访问在 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 委托方法中作为 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

我已经从 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

更新4: 将 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 错误,无法与服务器建立安全连接。

【问题讨论】:

  • 嗨 Uma,我已经尝试了几乎所有这些键和值,但没有什么能解决我的问题
  • 你为什么要使用这个 NSExceptionDomainsxx.xx.xxx.xxx
  • 我在尝试不同的东西,只是尝试了这个看看它是否有效
  • 它应该按照 ATS 上的苹果文档工作,因为服务器有 https,但我收到 9802 错误

标签: ios swift https tls1.2


【解决方案1】:

除了已在其他答案和 cmets 中解决的 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 证书的代码 sn-p,以防您处理此类证书。

要在 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
    ...
}   

【讨论】:

  • 嗨,Magma,感谢您的回复,我如何使用此代码,因为它既不是客观 c 也不是 swift(可能是 c)?
  • @pankaj 我已经用一个快速集成示例更新了我的答案。
  • 我已经进行了您建议的更改,还将 URL 名称从“www.example.com”更改为“xx.xx.xxx.xxx”,但我仍然收到 400 错误请求NSAllowsArbitraryLoads 为真
  • 当 NSAllowsArbitraryLoads 被删除时,我得到以下信息:NSURLSession/NSURLConnection HTTP 加载失败 (kCFStreamErrorDomainSSL, -9802) 错误发生 SSL 错误,无法与服务器建立安全连接。
【解决方案2】:

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

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

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

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

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

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

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

【讨论】:

  • 我意识到在测试中,您不需要删除和添加字典条目 - 您可以根据需要更改值。就我而言, NSRequiresCertificateTransparency 是修复它的那个。当然,您的里程可能会有所不同。
  • 另请参阅稍后添加的 nscurl 调用,以帮助您找到正确的设置。
  • 我无法使用 nscurl,因为我没有 El Capitain
  • 还有其他方法可以访问吗?
  • 为什么不让安装了 El Capitan 的其他人为您运行它?
【解决方案3】:

如果您不告诉我们真实的 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>

【讨论】:

  • 即使我给你 url,它也不适合你,因为它是在一个安全的网络上,我不需要使用 NSAppTransportSecurity,因为 web 服务是 https。我和在服务器端实现它的人谈过,他说我不需要通过任何证书或身份验证细节。他说我在 SNI 中发送了一些相互矛盾的值。我在更新 1 中的服务器上给出了上述错误
  • 他也是说web服务同时支持tls1.1和tls1.2
  • 我觉得不能同时是1.1和1.2,做个curl -v xx.xx.xxx.xxx
  • 如果您不满足我在回答中引用的所有苹果要求,即使您使用 https,您也可能需要 NSAppTransportSecurity
  • 我在终端中做了 curl -v xx.xx.xxx.xxx:443 并得到了以下结果: curl -v xx.xx.xxx.xxx:443 * 将 URL 重建为:xx.xx.xxx.xxx:443 * 正在尝试 xx.xx.xxx.xxx... * 已连接到 xx.xx.xxx.xxx (xx.xx.xxx.xxx) 端口 443 (#0) * 警告:使用 IP 地址,SNI 被操作系统禁用。 * SSL 证书问题:无效的证书链 * 关闭连接 0 curl:(60) SSL 证书问题:无效的证书链更多详情:curl.haxx.se/docs/sslcerts.html
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-16
  • 2019-08-04
  • 2016-10-01
  • 1970-01-01
  • 2017-12-01
  • 2023-03-20
相关资源
最近更新 更多