无法从iOS访问https web服务

16

我正在尝试访问一个使用https协议的Web服务。最初我遇到了以下错误:

NSURLSession/NSURLConnection HTTP加载失败(kCFStreamErrorDomainSSL,-9802) errorAn SSL error has occurred and a secure connection to the server cannot be made.

我通过在我的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:error:140790E5:SSL routines:SSL23_WRITE:ssl handshake failure:/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) error 发生了SSL错误,无法建立到服务器的安全连接。


请使用以下链接进行检查:https://dev59.com/o10Z5IYBdhLWcg3wnBb_#32714466 - Uma Madhavi
嗨Uma,我已经尝试了几乎所有的键和值,但是没有什么能解决我的问题。 - pankaj
你为什么要使用这个<key>NSExceptionDomains</key>? <dict> <key>xx.xx.xxx.xxx</key> <dict> - Uma Madhavi
我正在尝试不同的东西,只是想试一下看看它是否有效。 - pankaj
根据苹果文档中的ATS,它应该可以正常工作,因为服务器具有https,但我收到了9802错误。 - pankaj
显示剩余3条评论
3个回答

2

我遇到了和你一样的问题,并且能够解决它——主要是根据你所做的研究、发布在这里的信息以及Steven Peterson提供的背景信息。

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

NSAppTransportSecurity settings

然后,只需要找出哪些设置提供了解决方案。这就是找出哪个设置被删除后再次出现问题的事情。

当然,这在你的情况下可能是不同的。

请注意,我使用名称而不是数字来指定域名,正如之前由magma指出的那样。

另外(至少在我的情况下),解决这个问题并不涉及编码。

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

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

我意识到在测试中,您不需要删除和添加字典条目 - 您只需根据需要更改值即可。在我的情况下,NSRequiresCertificateTransparency是解决问题的关键。当然,您的情况可能会有所不同。 - ecotax
还可以查看后面添加的nscurl调用,以帮助您找到正确的设置。 - ecotax
我无法使用 nscurl,因为我没有 El Capitain。 - pankaj
有其他访问它的方式吗? - pankaj
为什么不让安装了El Capitan系统的其他人为您运行它呢? - ecotax
我很想帮忙,但是我在办公室网络上没有任何安装El Capitan的系统。 :( - pankaj

1
如果您不告诉我们真实的URL,那么帮助您将会变得困难。
以下是苹果公司安全连接的要求:
这些是应用程序传输安全性要求:服务器必须支持至少传输层安全(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>

即使您使用https,如果您未满足我在答案中引用的所有苹果要求,您可能仍需要NSAppTransportSecurity。 - jcesarmobile
我在终端中执行了curl -v https://xx.xx.xxx.xxx:443命令,并得到以下结果: curl -v https://xx.xx.xxx.xxx:443
  • 重建URL为:https://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证书问题:无效的证书链 更多详情请参见:http://curl.haxx.se/docs/sslcerts.html
- pankaj
我已经添加了NSAppTransportSecurity并将其更新到我的问题中,作为更新:4,但对我仍然没有用。 - pankaj
好的,xx.xx.xxx.xxx是一个IP地址,我不认为ATS支持IP地址。而且你的证书无效。 - jcesarmobile
让我们在聊天中继续这个讨论 - jcesarmobile
显示剩余6条评论

1
除了其他答案和评论中已经解决的ATS问题之外,似乎您正在尝试通过其IP地址连接到SSL服务器。以下参考资料可能有用(我从苹果的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文件添加到您的项目中(文件..新建..文件..iOS/源/C文件),例如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
    ...
}   

@pankaj 请查看 https://dev59.com/EGAg5IYBdhLWcg3wE3nQ 和 http://spin.atomicobject.com/2015/02/23/c-libraries-swift/。 - magma
@pankaj 我已经更新了我的答案,并附上了一个Swift集成示例。 - magma
当删除NSAllowsArbitraryLoads时,我会得到以下内容:NSURLSession / NSURLConnection HTTP加载失败(kCFStreamErrorDomainSSL,-9802)错误。发生了SSL错误,无法与服务器建立安全连接。 - pankaj
你的意思是说它不能使用 IP 地址工作吗?我没有主机名,只能通过 IP 地址访问已创建的服务。 - pankaj
@pankaj,你可以在URL中使用IP地址(以便通过TCP连接),但是请在我提供的代码中使用服务器的主机名(以便与证书中的服务器主机名匹配)。 - magma
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接