Wingzero
Wingzero

Reputation: 9754

kCFStreamErrorDomainSSL -9802 error but it's HTTPS URL

So I know the ATS stuff and how to edit the info.plist to allow HTTP. However, the URL is https://api.map.baidu.com/api?v=2. 0&ak=1XjLLEhZhQNUzd93EjU5nOGQ&s=1, which is a HTTPS request, but I still get

NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9802)

Then I add setenv("CFNETWORK_DIAGNOSTICS", "3", 1); in didFinishLaunchingWithOptions to enable verbose log.

In the log, I find the error log:

5510 Jan 14 10:52:01  MCompass[8549] <Notice>: CFNetwork Diagnostics [3:363] 10:52:01.458 {
5511     Response Error
5512     Request: <CFURLRequest 0x7fecf3cddcb0 [0x10aff37b0]> {url = https://api.map.baidu.com/api?v=2.0&ak=1XjLLEhZhQNUzd93EjU5nOGQ&s=1, cs = 0x0}
5513       Error: Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0,                                   kCFStreamPropertySSLPeerTrust=<SecTrustRef: 0x7fecf406bbf0>, _kCFNetworkCFStreamSSLErrorOriginalValue=-9802, _kCFStreamErrorDomainKey=3,                         _kCFStreamErrorCodeKey=-9802, kCFStreamPropertySSLPeerCertificates=<CFArray 0x7fecf406cda0 [0x10aff37b0]>{type = immutable, count = 3, values = (
5514                 0 : <cert(0x7fecf3fa80e0) s: baidu.com i: VeriSign Class 3 International Server CA - G3>
5515                 1 : <cert(0x7fecf3fa8920) s: VeriSign Class 3 International Server CA - G3 i: VeriSign Class 3 Public Primary Certification Authority - G5>
5516                 2 : <cert(0x7fecf4069fd0) s: VeriSign Class 3 Public Primary Certification Authority - G5 i: Class 3 Public Primary Certification Authority>
5517              )}}
5518     } [3:363]                                                                                                                                               
5519 Jan 14 10:52:01  MCompass[8549] <Notice>: CFNetwork Diagnostics [3:364] 10:52:01.459 {
5520                Did Fail
5521                  Loader: <CFMutableURLRequest 0x7fecf3cdd9f0 [0x10aff37b0]> {url = https://api.map.baidu.com/api?v=2.0&ak=1XjLLEhZhQNUzd93EjU5nOGQ&s=1, cs       = 0x0}
5522                   Error: Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0,                       kCFStreamPropertySSLPeerTrust=<SecTrustRef: 0x7fecf406bbf0>, _kCFNetworkCFStreamSSLErrorOriginalValue=-9802, _kCFStreamErrorDomainKey=3,                         _kCFStreamErrorCodeKey=-9802, kCFStreamPropertySSLPeerCertificates=<CFArray 0x7fecf406cda0 [0x10aff37b0]>{type = immutable, count = 3, values = (
5523                             0 : <cert(0x7fecf3fa80e0) s: baidu.com i: VeriSign Class 3 International Server CA - G3>
5524                             1 : <cert(0x7fecf3fa8920) s: VeriSign Class 3 International Server CA - G3 i: VeriSign Class 3 Public Primary Certification          Authority - G5>
5525                             2 : <cert(0x7fecf4069fd0) s: VeriSign Class 3 Public Primary Certification Authority - G5 i: Class 3 Public Primary                  Certification Authority>
5526                          )}}
5527     init to origin load: 0.00280595s
5528              total time: 0.447458s
5529             total bytes: 0
5530     } [3:364]

I am confused, because it's HTTPS request, but still have the issue. I tried the URL on Chrome, it is returning a valid cert (I have the cert knowledge like X509). But cannot figure out why it is blocked.

Could someone help? Thank in advance. Add this domain into ATS exceptions will help, but I don't want to add it, because it's HTTPS already!

UPDATE:

Running

/usr/bin/nscurl --ats-diagnostics -v "https://api.map.baidu.com/api?v=2.0&ak=1XjLLEhZhQNUzd93EjU5nOGQ&s=1"

Will return ALL PASS:

Xuans-MacBook-Pro:~ xuan$ /usr/bin/nscurl --ats-diagnostics -v "https://api.map.baidu.com/api?v=2.0&ak=1XjLLEhZhQNUzd93EjU5nOGQ&s=1"
Starting ATS Diagnostics

Configuring ATS Info.plist keys and displaying the result of HTTPS loads to https://api.map.baidu.com/api?v=2.0&ak=1XjLLEhZhQNUzd93EjU5nOGQ&s=1.
A test will "PASS" if URLSession:task:didCompleteWithError: returns a nil error.
================================================================================

Default ATS Secure Connection
---
ATS Default Connection
ATS Dictionary:
{
}
Result : PASS
---

================================================================================

Allowing Arbitrary Loads

---
Allow All Loads
ATS Dictionary:
{
    NSAllowsArbitraryLoads = true;
}
Result : PASS
---

================================================================================

Configuring TLS exceptions for api.map.baidu.com

---
TLSv1.2
ATS Dictionary:
{
    NSExceptionDomains =     {
        "api.map.baidu.com" =         {
            NSExceptionMinimumTLSVersion = "TLSv1.2";
        };
    };
}
Result : PASS
---

---
TLSv1.1
ATS Dictionary:
{
    NSExceptionDomains =     {
        "api.map.baidu.com" =         {
            NSExceptionMinimumTLSVersion = "TLSv1.1";
        };
    };
}
Result : PASS
---

---
TLSv1.0
ATS Dictionary:
{
    NSExceptionDomains =     {
        "api.map.baidu.com" =         {
            NSExceptionMinimumTLSVersion = "TLSv1.0";
        };
    };
}
Result : PASS
---

================================================================================

Configuring PFS exceptions for api.map.baidu.com

---
Disabling Perfect Forward Secrecy
ATS Dictionary:
{
    NSExceptionDomains =     {
        "api.map.baidu.com" =         {
            NSExceptionRequiresForwardSecrecy = false;
        };
    };
}
Result : PASS
---

================================================================================

Configuring PFS exceptions and allowing insecure HTTP for api.map.baidu.com

---
Disabling Perfect Forward Secrecy and Allowing Insecure HTTP
ATS Dictionary:
{
    NSExceptionDomains =     {
        "api.map.baidu.com" =         {
            NSExceptionAllowsInsecureHTTPLoads = true;
            NSExceptionRequiresForwardSecrecy = false;
        };
    };
}
Result : PASS
---

================================================================================

Configuring TLS exceptions with PFS disabled for api.map.baidu.com

---
TLSv1.2 with PFS disabled
ATS Dictionary:
{
    NSExceptionDomains =     {
        "api.map.baidu.com" =         {
            NSExceptionMinimumTLSVersion = "TLSv1.2";
            NSExceptionRequiresForwardSecrecy = false;
        };
    };
}
Result : PASS
---

---
TLSv1.1 with PFS disabled
ATS Dictionary:
{
    NSExceptionDomains =     {
        "api.map.baidu.com" =         {
            NSExceptionMinimumTLSVersion = "TLSv1.1";
            NSExceptionRequiresForwardSecrecy = false;
        };
    };
}
Result : PASS
---

---
TLSv1.0 with PFS disabled
ATS Dictionary:
{
    NSExceptionDomains =     {
        "api.map.baidu.com" =         {
            NSExceptionMinimumTLSVersion = "TLSv1.0";
            NSExceptionRequiresForwardSecrecy = false;
        };
    };
}
Result : PASS
---

================================================================================

Configuring TLS exceptions with PFS disabled and insecure HTTP allowed for api.map.baidu.com

---
TLSv1.2 with PFS disabled and insecure HTTP allowed
ATS Dictionary:
{
    NSExceptionDomains =     {
        "api.map.baidu.com" =         {
            NSExceptionAllowsInsecureHTTPLoads = true;
            NSExceptionMinimumTLSVersion = "TLSv1.2";
            NSExceptionRequiresForwardSecrecy = false;
        };
    };
}
Result : PASS
---

---
TLSv1.1 with PFS disabled and insecure HTTP allowed
ATS Dictionary:
{
    NSExceptionDomains =     {
        "api.map.baidu.com" =         {
            NSExceptionAllowsInsecureHTTPLoads = true;
            NSExceptionMinimumTLSVersion = "TLSv1.1";
            NSExceptionRequiresForwardSecrecy = false;
        };
    };
}
Result : PASS
---

---
TLSv1.0 with PFS disabled and insecure HTTP allowed
ATS Dictionary:
{
    NSExceptionDomains =     {
        "api.map.baidu.com" =         {
            NSExceptionAllowsInsecureHTTPLoads = true;
            NSExceptionMinimumTLSVersion = "TLSv1.0";
            NSExceptionRequiresForwardSecrecy = false;
        };
    };
}
Result : PASS
---

================================================================================

Upvotes: 3

Views: 7518

Answers (2)

Carl Lindberg
Carl Lindberg

Reputation: 2947

I don't know if this would fix your problem, but I had a similar issue recently. In my case, I also had a server which passed nscurl --ats-diagnostics with a PASS on every one, but failed with an ATS -9802 error in the app. The servers used TLS version 1.2, had forward secrecy, used good cipher suites, and had a SHA256 cert.

The SSL Labs page had the hint which pointed to the answer -- it said everything was good, but the SSL chain was incomplete. The server was slightly misconfigured, as it was providing the correct cert, but not an intermediate cert it needed to connect to the root cert (which should already be on the client). There were pointers to where the intermediate cert could be downloaded, so the SSL Labs page did that, and only downgraded the grade to a "B" as a result. But, that means a client implementation also needs to be able to download intermediate certs on its own -- not all implementations do.

In my case, because this was in a testing/development environment where we had issues with other servers sporadically being misconfigured, we had set the AFSecurityPolicy property validatesDomainName to NO, as that got around these other issues (it is of course YES in production). But, that also became the setting for this server as well, which was not strictly necessary. That in turn means that AFSecurityPolicy uses SecPolicyCreateBasicX509() instead of SecPolicyCreateSSL() when it configures the SecTrustRef. That is mostly OK, except the header documentation for SecTrustGetNetworkFetchAllowed() states:

By default, network fetch of missing certificates is enabled if the trust evaluation includes the SSL policy, otherwise it is disabled.

So, that was the problem. nscurl will use the SSL policy, so it would download the intermediate certificate and work fine. But, with that flag being turned off, ATS would fail at runtime, as SecTrustEvaluate() would return kSecTrustResultRecoverableTrustFailure, which without further intervention will be deemed a failure. If I set validatesDomainName back to YES, then it started working (on this server). Or, if you have a handle on the SecTrustRef after the policy gets added to it, you can call

SecTrustSetNetworkFetchAllowed(trustRef, true);

as that will also allow App Transport Security to fetch the intermediate certificate even with the X509 policy. Or, you can fix the server configuration to provide the entire certificate chain up to but not including the root certificate, like it is supposed to.

EDIT: The SecTrustSetNetworkFetchAllowed call only works on iOS10. For iOS9, I had to call SecTrustSetPolicies() with an SSL policy, i.e. reset the policy list -- that was the only way that the missing certificates would be fetched by ATS on iOS9.

Upvotes: 1

darkkean
darkkean

Reputation: 46

As discussed on this answer, by simply accessing your API url by HTTPS does not mean it will comply with Apple's ATS. I also use nscurl, but I believe the tool has not yet matured enough and may be quite inefficient at times.

SSL Labs test is far more better and detailed, imho. It will help you track down what's lacking in your SSL configuration.

Note that ATS requires TLS 1.2 at minimum and Perfect Forward Secrecy cipher suites.

Upvotes: 2

Related Questions