16

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.

halfer
  • 19,824
  • 17
  • 99
  • 186
pankaj
  • 7,878
  • 16
  • 69
  • 115
  • Check with this link http://stackoverflow.com/questions/31254725/transport-security-has-blocked-a-cleartext-http/32714466#32714466 – Uma Madhavi Oct 05 '15 at 10:36
  • hi Uma, I have tried almost all of these keys and values but nothing could solve my problem – pankaj Oct 05 '15 at 11:30
  • Why are u using this NSExceptionDomains xx.xx.xxx.xxx – Uma Madhavi Oct 05 '15 at 11:33
  • I was trying different things and just tried this to see if it works – pankaj Oct 05 '15 at 11:47
  • It should have worked as per apple docs on ATS as server has https but I am getting 9802 error – pankaj Oct 05 '15 at 11:50
  • If 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 – jcesarmobile Oct 07 '15 at 06:19
  • I am sorry I don't have El Capitan, Is there any other utility with which I can use it, 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: – pankaj Oct 07 '15 at 06:26
  • If you don't have El Capitan, then the easiest solutions probably are: 1) do as I did, use all settings as I specified, and if it works, then change them one by one to see to see which one breaks it, or 2) just ask someone with El Capitan installed to run the command for you and copypaste and mail the result. – ecotax Oct 07 '15 at 08:33

3 Answers3

2

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
ecotax
  • 1,933
  • 17
  • 22
  • I realised that in testing, you don't need to remove and add dicionary entries - you can just change the values as appropriate. In my case, the NSRequiresCertificateTransparency was the one that fixed it. Your mileage may vary of course. – ecotax Oct 07 '15 at 07:58
  • Also see the nscurl call added later to help you find the correct settings. – ecotax Oct 07 '15 at 08:19
  • I am not able to use nscurl as I don't have El Capitain – pankaj Oct 07 '15 at 08:26
  • is there any other way to access it? – pankaj Oct 07 '15 at 08:29
  • Why don't you just let someone else with El Capitan installed run it for you? – ecotax Oct 07 '15 at 08:41
  • I wish I could but I don't have any system with El Capitan here on my office network. :( – pankaj Oct 07 '15 at 11:04
1

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>
Michael Dorner
  • 17,587
  • 13
  • 87
  • 117
jcesarmobile
  • 51,328
  • 11
  • 132
  • 176
  • Even if I give you the url, it will not work for you as it is on a secured network, I don't need to use NSAppTransportSecurity as the web service is https. I have talked with guy who implemented it on server side and he says that I don't need to pass any certificate or authentication detail. He says that I am sending some conflicting values in SNI. I have given above error on server in update 1 – pankaj Oct 07 '15 at 06:30
  • He is also saying that web service supports both tls1.1 and tls1.2 – pankaj Oct 07 '15 at 06:32
  • I don't think it can be 1.1 and 1.2 at the same time, do a `curl -v xx.xx.xxx.xxx` – jcesarmobile Oct 07 '15 at 06:36
  • And you might need NSAppTransportSecurity even if you use https, if you don't fulfill all apple requisites I quoted in my answer – jcesarmobile Oct 07 '15 at 06:38
  • I did curl -v https://xx.xx.xxx.xxx:443 in terminal and got following: curl -v https://xx.xx.xxx.xxx:443 * Rebuilt URL to: https://xx.xx.xxx.xxx:443/ * Trying xx.xx.xxx.xxx... * Connected to xx.xx.xxx.xxx (xx.xx.xxx.xxx) port 443 (#0) * WARNING: using IP address, SNI is being disabled by the OS. * SSL certificate problem: Invalid certificate chain * Closing connection 0 curl: (60) SSL certificate problem: Invalid certificate chain More details here: http://curl.haxx.se/docs/sslcerts.html – pankaj Oct 07 '15 at 06:42
  • I have added NSAppTransportSecurity and updated it to my question as Update:4, but it is still not working for me – pankaj Oct 07 '15 at 06:50
  • ok, so xx.xx.xxx.xxx is an IP address, I don't think ATS supports IP addresses. And your certificate is not valid. – jcesarmobile Oct 07 '15 at 06:50
  • does it work with just `NSAppTransportSecurity NSAllowsArbitraryLoads `? – jcesarmobile Oct 07 '15 at 06:52
  • I assume the ciphers that you have mentioned above should be used on server? – pankaj Oct 07 '15 at 06:52
  • no it gives 400 bad request as the service is in https – pankaj Oct 07 '15 at 06:53
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/91558/discussion-between-jcesarmobile-and-pankaj). – jcesarmobile Oct 07 '15 at 06:58
1

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
    ...
}   
magma
  • 8,432
  • 1
  • 35
  • 33
  • Hi Magma, thanks for replying, How do I use this code as it is neither objective c nor swift(probably c)? – pankaj Oct 07 '15 at 07:26
  • @pankaj see http://stackoverflow.com/questions/24004732/how-to-call-c-from-swift and http://spin.atomicobject.com/2015/02/23/c-libraries-swift/ – magma Oct 07 '15 at 07:34
  • @pankaj I've updated my answer with an example swift integration. – magma Oct 07 '15 at 08:00
  • I have made the changes suggested by you also changed the the url name from "www.example.com" to "xx.xx.xxx.xxx" but I am still get getting 400 bad request when NSAllowsArbitraryLoads is true – pankaj Oct 07 '15 at 08:17
  • I get following when NSAllowsArbitraryLoads is removed: NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9802) errorAn SSL error has occurred and a secure connection to the server cannot be made. – pankaj Oct 07 '15 at 08:17
  • no; the url name should be the server's *hostname*, **not** an ip address — otherwise, what's the point? `NSAllowsArbitraryLoads` should be false, you want to use SSL. Also, are you using a self-signed cert? – magma Oct 07 '15 at 08:18
  • you mean it will not work with ip address? I don't have host name, the service that have been created can only be accessed by ip address – pankaj Oct 07 '15 at 08:21
  • @pankaj you can use the ip address in your url (so that you can connect via TCP), but use the server's **hostname** in the code I provided (so that it matches with the server's hostname in the certificate) – magma Oct 07 '15 at 08:23
  • I am trying to contact the person who worked on server side to provide me hostname. You also asked about self signed certificate, yes it is being used on server. I also want to add that when I run openssl command: openssl s_client -showcerts -connect 10.70.101.165:443 – pankaj Oct 07 '15 at 11:10
  • Did anyone get this working with URLSession? I successfully call this method but I still get the 400 error. – MatzeLoCal Jun 30 '17 at 19:35