1

THIS IS NOT A REPEATED QUESTION, READ ON. I am upgrading the deprecated code in my app to iOS10 compliance.

THE ERROR: NSURLSession is giving me trouble, with this infamous error, along with 9806 and 9801, depending on what I put on the .plist file:

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

MY CODE: In my Info.plist file, I have:

<key>NSAppTransportSecurity</key>
    <dict>
        <key>NSExceptionDomains</key>
        <dict>
            <key>https://my.own-server.com</key>
            <dict>
                <key>NSIncludesSubdomains</key>
                <true/>
                <key>NSExceptionAllowInsecureHTTPSLoads</key>
                <true/>
                <key>NSExceptionRequiresForwardSecrecy</key>
                <true/>
                <key>NSExceptionMinimumTLSVersion</key>
                <string>TLSv1.0</string>
                <key>NSThirdPartyExceptionAllowInsecureHTTPSLoads</key>
                <false/>
                <key>NSThirdPartyExceptionRequiresForwardSecrecy</key>
                <false/>
                <key>NSThirdPartyExceptionMinimumTLSVersion</key>
                <string>TLSv1.0</string>
                <key>NSRequiresCertificateTransparency</key>
                <false/>
            </dict>
        </dict>
        <key>NSAppTransportSecurity</key>
        <dict>
            <key>NSAllowsArbitraryLoads</key>
            <false/>
        </dict>
    </dict>

In my ObjectiveC code, I have this:

NSURLSessionConfiguration *defaultConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session __unused = [NSURLSession sessionWithConfiguration:defaultConfiguration delegate:[PortalRequest alloc] delegateQueue:[NSOperationQueue mainQueue]]; operationQueue:[NSOperationQueue mainQueue]];
    requestContainer.sessionDataTask = [session dataTaskWithRequest:request];
[self.sessionDataTask resume];

In the DELEGATE method of the class where make the URLSession call, I can see didReceiveChallenge:

LOG: ** NSURLSession IOS10 ** - didReceiveChallenge called

...and finally I get the error:

[TIMESTAMP] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9806)

THE SERVER None of the solutions proposed in other answers work, is it because of Apple's 2017 deadline? https://techcrunch.com/2016/06/14/apple-will-require-https-connections-for-ios-apps-by-the-end-of-2016/ According to this online security analysis tool, https://www.ssllabs.com/ssltest/analyze.html The backend DOES support TLS 1.2

Any ideas on how to solve this?? Do you know where to find some iOS sample code to be 100% sure the endpoint I am pointing at is guilt free?

UPDATE This code works for me, any opinions on it?:

- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler{
    NSLog(@"*** KBRequest.NSURLSessionDelegate - didReceiveChallenge IOS10");

    if([challenge.protectionSpace.authenticationMethod
        isEqualToString:NSURLAuthenticationMethodServerTrust])
    {
        if([challenge.protectionSpace.host
            isEqualToString:@"engine.hello-indigo.com"])
        {
            NSURLCredential *credential =
            [NSURLCredential credentialForTrust:
             challenge.protectionSpace.serverTrust];
            completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
        }
        else
            completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
    }
}
Josh
  • 6,251
  • 2
  • 46
  • 73
  • If you're implementing your own auth challenge handling, my first guess would be a bug in that. Can you post the contents of that method? – dgatwood Jan 30 '17 at 17:50
  • How u solve your problem? bacause i have http audio stream and have NSURLConnection finished with error - code -1002 – Genevios Oct 12 '17 at 13:09
  • @Genevios I solved it, look at the Update on my question. It works, and nobody suggests a better idea, so I'll consider it good. About your code 1002 error, check that you are using https in your URL https://stackoverflow.com/questions/26647423/nsurlerrordomain-error-code-1002-description?rq=1 – Josh Oct 15 '17 at 19:34
  • @Josh Oh my friend i use audio stream, and how i am understand audio stream don't have https...it's true or not? how u think? – Genevios Oct 16 '17 at 06:53
  • @Genevios sounds complicated, but you are sending this audio stream to some server address right? This address must start with http or https. – Josh Oct 16 '17 at 10:48
  • Yes man, my adress start from http but i get the same error again..=\ – Genevios Oct 17 '17 at 10:34
  • Did you try your address as both Http and then Https ? @Genevios – Josh Oct 17 '17 at 11:00
  • Yes, but if i use https my request don't work because i don't have https on the server.. – Genevios Oct 17 '17 at 12:18
  • @Genevios in that case, adding that server as an execption to the .plist file may work. – Josh Oct 17 '17 at 13:54
  • How i can do this? I already saw many examples but no one perfect. I mean what line i must add? – Genevios Oct 18 '17 at 07:35
  • Right clic your info.plist file > open > as source. (my .plist was inside the Supporting Files folder), and try adding your url exception, following these two examples: http://txt.do/d4fix – Josh Oct 18 '17 at 07:51
  • Awesome i ll try to paste this – Genevios Nov 04 '17 at 14:17

3 Answers3

1

To avoid breaking TLS completely (not to mention other forms of authentication), you should be doing something more like this:

- (void)URLSession:(NSURLSession *)session
    didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
      completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
                                  NSURLCredential *credential))completionHandler
    {
        NSURLProtectionSpace *protectionSpace = [challenge protectionSpace];
        if ([protectionSpace authenticationMethod] == NSURLAuthenticationMethodServerTrust) {
            // Load our trusted root certificate from the app bundle.
            SecTrustRef trust = [protectionSpace serverTrust];
            NSURL *certificatePath = [[NSBundle mainBundle] URLForResource:@"customRootCert" ofType:@"der"];
            NSData *certificateData = [NSData dataWithContentsOfURL:resourcePath];
            SecCertificateRef trustedCert = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData);

            // Change the trust object to trust our root cert.
            trust = addAnchorToTrust(trust, trustedCert);

            SecTrustResultType secresult = kSecTrustResultInvalid;
            if (SecTrustEvaluate(trust, &secresult) != errSecSuccess) {
                // Something went horribly wrong.
                completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
                return;
            }

            switch (secresult) {
                case kSecTrustResultUnspecified: // The OS trusts this certificate implicitly.
                case kSecTrustResultProceed: // The user explicitly told the OS to trust it.
                {
                    NSURLCredential *credential =
                        [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                    completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, credential)
                    return;
                }
                default:
                    /* It's somebody else's key/cert. Fall through. */
            }
            completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
        } else {
            // If we aren't checking the server's public key, just use
            // the default behavior.
            completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
        }
    }

Note that this code is uncompiled and untested, so feel free to fix typos and other errors.

dgatwood
  • 10,129
  • 1
  • 28
  • 49
  • I have written a snippet that also works, based on your suggestion (and added it to the question above). Do you think it is safe? Thanks. – Josh Feb 06 '17 at 13:02
  • 1
    No, it is not safe. The code you posted means that anyone can provide any certificate with any key for that hostname. It effectively reduces your app's request security to unencrypted HTTP, and if you're going to do that, you might as well just use HTTP and ask for an exception (good luck). – dgatwood Feb 06 '17 at 17:24
0

Try to set NSExceptionRequiresForwardSecrecy to false. However your server do meet the min requirement for Transport Layer Security (TLS) protocol version, your server Connection-Cipher may not support forward secrecy.

The negotiated Transport Layer Security (TLS) version must be TLS 1.2. Attempts to connect without TLS/SSL protection, or with an older version of TLS/SSL, are denied by default.

The connection must use either the AES-128 or AES-256 symmetric cipher.

The negotiated TLS connection cipher suite must support perfect forward secrecy (PFS) through Elliptic Curve Diffie-Hellman Ephemeral (ECDHE) key exchange, and must be one of the following:

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

Idali
  • 1,023
  • 7
  • 10
  • The security tool shows all those cipher suits are supported. The error happens right after the delegate method didReceiveChallenge is called (as shown in my latest edit above) – Josh Jan 25 '17 at 16:30
0

I finally solved the issue! I took out the exceptions from the .plist file, and added this code to the didReceiveChallenge Delegate method:

- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler{
    NSLog(@"*** KBRequest.NSURLSessionDelegate - didReceiveChallenge IOS10");
    completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:[[challenge protectionSpace] serverTrust]]);
}

This article was very useful: http://timekl.com/blog/2015/08/21/shipping-an-app-with-app-transport-security/

Josh
  • 6,251
  • 2
  • 46
  • 73
  • @dgatwood you are right, but nobody explains anywhere how to do it "properly". If I still have time, I will try to fix it. For now, I need to regain speed after all the time I wasted on this pitfall, and get my app on the store. – Josh Feb 04 '17 at 17:35
  • 1
    You absolutely cannot ship this on the app store with this hack. Your app will almost certainly get rejected. I've posted an example of a completion handler that loads a custom root cert from your app bundle and validates requests against that. – dgatwood Feb 04 '17 at 21:42
  • Would you mind sharing your example as an answer? I promise a green tick if it works. @dgatwood – Josh Feb 06 '17 at 08:48