16
votes

I am trying to access a web service which is available on https protocol. Initially I was getting following error:

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

I fixed it by adding following in my 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>

But now I am getting following as html in response in connectionDidFinishLoading delegate method:

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

I am using following to set up trust with server:

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)
    }
}

Update 1

The server logs are showing following error:

Hostname xx.xx.xxx.xx provided via SNI and hostname my_secured_host_name provided via HTTP are different

How can I add hostname in SNI?

Update 2

I have removed key to by pass http from info.plist as the service is already a https

Update 3

When I tried with openssl as

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

but I am getting following error:

CONNECTED(00000003) 8012:error:140790E5:SSL routines:SSL23_WRITE:ssl handshake failure:/SourceCache/OpenSSL098/OpenSSL098-52.40.1/src/ssl/s23_lib.c:185

Update4: Changed Info.plist to following:

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

Still getting following error:

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

3
hi Uma, I have tried almost all of these keys and values but nothing could solve my problempankaj
Why are u using this <key>NSExceptionDomains</key> <dict> <key>xx.xx.xxx.xxx</key> <dict>Uma Madhavi
I was trying different things and just tried this to see if it workspankaj
It should have worked as per apple docs on ATS as server has https but I am getting 9802 errorpankaj

3 Answers

1
votes

On top of the ATS issues which are already addressed in other answers and comments, it appears that you're trying to connect to a SSL Server by its IP address. The following reference might be useful (I'm quoting verbatim from Apple's iOS Developer Library):

To override the hostname (to allow a certificate for one specific site to work for another specific site, or to allow a certificate to work when you connected to a host by its IP address), you must replace the policy object that the trust policy uses to determine how to interpret the certificate. To do this, first create a new TLS policy object for the desired hostname. Then create an array containing that policy. Finally, tell the trust object to use that array for future evaluation of trust.

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
}

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

Note that on the same page you can find another code snippet that deals with self-signed SSL certs, in case you're dealing with such a certificate.

To use this function in a Swift project, add a new C file to your project (File.. New.. File.. iOS/Source/C_File), e.g. mysectrust.c and the corresponding header mysectrust.h (if XCode asks you to create a bridging header, say yes):

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
}

Of course, replace www.example.com in the above code with your hostname.

Then, find the bridging header in your Xcode project, projectname-Bridging-Header.h, and append the following line:

#import "mysectrust.h"

Now you can just call this function from Swift, e.g.:

func whatever(trust: SecTrustRef){

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

I had the same problem as you, and I could solve it - mostly following what was already researched by you, posted here, and the background info provided by Steven Peterson.

I found that if I try connecting with the following settings, the -9802 error is gone:

NSAppTransportSecurity settings

Then it was just a matter of seeing which of those settings provides the solution. That's just a matter of finding out which one breaks it again if removed.

This may, of course, be a different one in your case.

Note that I do specify the domain by name, not by number, as pointed out by magma before. Also (at least in my case) no coding was involved in solving this problem.

Having solved it, I later learned that figuring out which settings to use can be automated:

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

If you don't tell us the real URL, it's going to be difficult to help you

These are Apple requeriments for secure connections:

These are the App Transport Security requirements: The server must support at least Transport Layer Security (TLS) protocol version 1.2. Connection ciphers are limited to those that provide forward secrecy (see the list of ciphers below.)

Certificates must be signed using a SHA256 or greater signature hash algorithm, with either a 2048-bit or greater RSA key or a 256-bit or greater Elliptic-Curve (ECC) key. Invalid certificates result in a hard failure and no connection. These are the accepted ciphers:

  • 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

So, at least one of them is failing

If you are not sure which one and you have El Capitan, just run the nscurl utility /usr/bin/nscurl --ats-diagnostics xx.xx.xxx.xxx and it will test all the possible keys and values and will tell you which ones work.

I had the same problem and the keys that worked for me where (my certificate was signed with SHA1 and SHA256 or greater is needed):

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