overcoming self-signed certificates? NSURLSession delegates not called.

My dev server uses a self-signed certificate (server public key is 1024 bit with uses TLSv1.0). I'm not able to get past the CFNetwork SSLHandshake failed (-9824) error. I was hoping to use SSL-pinning, but the delegate are never called.


I'm using NSURLSession and thought I could use the delegate method(s):

URLSession: didReceiveChallenge:completionHandler
URLSession:task:didReceiveChallenge:completionHandler

to process the SSL-pinning, but, neither gets called.

I've even removed the completionHandler in the

let task = session.dataTaskWithRequest(request)

but still, the delegates are not called.


Here's my info.plist:

<key>NSAppTransportSecurity</key>
    <dict>
<!--        <key>NSAllowsArbitraryLoads</key>-->
<!--        <true/>-->
        <key>NSExceptionDomains</key>
        <dict>
            <key>devserver.com</key>
            <dict>
                <!--Include to allow subdomains-->
                <key>NSIncludesSubdomains</key>
                <true/>
                <!--Include to allow HTTP requests-->
                <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
                <true/>
                <!--Include to specify minimum TLS version-->
                <key>NSTemporaryExceptionMinimumTLSVersion</key>
                <string>TLSv1.0</string>
                <key>NSTemporaryExceptionRequiresForwardSecrecy</key>
                    <false/>
            </dict>
        </dict>
    </dict>



Here's my class:


import Foundation
import UIKit
public class SANetworkServices: NSObject, NSURLSessionDataDelegate, NSURLSessionDelegate, NSURLSessionTaskDelegate {
    public func authenticateDevUser(customerID:String, userID:String, password:String, completion:(result:String) -> Void) {
        let parameters = ["companyID":customerID, "userID":userID, "password":password]
          let request = NSMutableURLRequest(URL: NSURL(string: appConfigInstance.loginURLString)!) /
        request.HTTPMethod = "POST"
        do {
            request.HTTPBody = try NSJSONSerialization.dataWithJSONObject(parameters, options: NSJSONWritingOptions.PrettyPrinted)
            request.addValue("application/json", forHTTPHeaderField: "Content-Type")
            request.addValue("application/json", forHTTPHeaderField: "Accept")
        } catch _ {
            print("something bad happened?")
            return
        }
        let config: NSURLSessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration()
        let session = NSURLSession(configuration: config)
        let task = session.dataTaskWithRequest(request) { (data, response, error) in
            print("\nResponse: \(response)")
            print("Error   : \(error?.localizedDescription)")
        }
        task.resume()
    }


    public func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveResponse response: NSURLResponse, completionHandler: (NSURLSessionResponseDisposition) -> Void) {
     // never called
    }
   
    public func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
// never called
        let serverTrust:SecTrustRef = challenge.protectionSpace.serverTrust!
        let certificate: SecCertificateRef = SecTrustGetCertificateAtIndex(serverTrust, 0)!
        let remoteCertificateData = CFBridgingRetain(SecCertificateCopyData(certificate))!
        let cerPath: String = NSBundle.mainBundle().pathForResource("dev-servercert", ofType: "der")!
        let localCertificateData = NSData(contentsOfFile:cerPath)!
     
        if (remoteCertificateData.isEqualToData(localCertificateData) == true) {
            let credential:NSURLCredential = NSURLCredential(forTrust: serverTrust)
            challenge.sender?.useCredential(credential, forAuthenticationChallenge: challenge)
            completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential, NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!))
         
        } else {
            challenge.sender?.cancelAuthenticationChallenge(challenge)
            completionHandler(NSURLSessionAuthChallengeDisposition.RejectProtectionSpace, nil)
        }
    }
    public func URLSession(session: NSURLSession,
        task: NSURLSessionTask,
        didReceiveChallenge challenge: NSURLAuthenticationChallenge,
        completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?)
        -> Void) {
// sigh, never called.
            let serverTrust:SecTrustRef = challenge.protectionSpace.serverTrust!
            let certificate: SecCertificateRef = SecTrustGetCertificateAtIndex(serverTrust, 0)!
            let remoteCertificateData = CFBridgingRetain(SecCertificateCopyData(certificate))!
            let cerPath: String = NSBundle.mainBundle().pathForResource("dev-servercert", ofType: "der")!
            let localCertificateData = NSData(contentsOfFile:cerPath)!
         
            if (remoteCertificateData.isEqualToData(localCertificateData) == true) {
                let credential:NSURLCredential = NSURLCredential(forTrust: serverTrust)
                challenge.sender?.useCredential(credential, forAuthenticationChallenge: challenge)
                completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential, NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!))
             
            } else {
                challenge.sender?.cancelAuthenticationChallenge(challenge)
                completionHandler(NSURLSessionAuthChallengeDisposition.RejectProtectionSpace, nil)
            }
    }

Am I missing something?


Thanks,

Answered by DTS Engineer in 46245022

My dev server uses a self-signed certificate ….

Just FYI, that’s a bad idea because it’s very easy to leave this debugging code in your app, and thus ship insecure software to your end users (some surprisingly well-known app developers have made this mistake!). A better approach is to create a development certificate authority and install that CA’s root certificate on your device. That reduces the code you need to write and ensures there will be no security mishaps.

Technote 2326 Creating Certificates for TLS Testing explains how to do this.

I'm not able to get past the CFNetwork SSLHandshake failed (-9824) error [because] the delegate are never called.

Your App Transport Security dictionary is a little wacky. The keys you’re using are not the ones documented in the App Transport Security Technote.

Also, if you have

NSExceptionAllowsInsecureHTTPLoads
set, you don’t need to set anything else. OTOH, if you follow my advice and use a custom CA you might be able to avoid having an ATS dictionary at all (although it’s more likely you’ll need to set both
NSExceptionAllowsInsecureHTTPLoads
and
NSExceptionRequiresForwardSecrecy
).

However, to address your direct question, your problem is this line of code.

let session = NSURLSession(configuration: config)

When you create the session you need to pass in a delegate. If you don’t, NSURLSession has no idea what object to send the authentication challenges to.

Share and Enjoy

Quinn "The Eskimo!"
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
Accepted Answer

My dev server uses a self-signed certificate ….

Just FYI, that’s a bad idea because it’s very easy to leave this debugging code in your app, and thus ship insecure software to your end users (some surprisingly well-known app developers have made this mistake!). A better approach is to create a development certificate authority and install that CA’s root certificate on your device. That reduces the code you need to write and ensures there will be no security mishaps.

Technote 2326 Creating Certificates for TLS Testing explains how to do this.

I'm not able to get past the CFNetwork SSLHandshake failed (-9824) error [because] the delegate are never called.

Your App Transport Security dictionary is a little wacky. The keys you’re using are not the ones documented in the App Transport Security Technote.

Also, if you have

NSExceptionAllowsInsecureHTTPLoads
set, you don’t need to set anything else. OTOH, if you follow my advice and use a custom CA you might be able to avoid having an ATS dictionary at all (although it’s more likely you’ll need to set both
NSExceptionAllowsInsecureHTTPLoads
and
NSExceptionRequiresForwardSecrecy
).

However, to address your direct question, your problem is this line of code.

let session = NSURLSession(configuration: config)

When you create the session you need to pass in a delegate. If you don’t, NSURLSession has no idea what object to send the authentication challenges to.

Share and Enjoy

Quinn "The Eskimo!"
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

@eskimo -


My company uses its own internal CA for cert creation due to the majority of our apps being internally distributed. We have installed the CA root cert on our test devices as you suggested above, "A better approach is to create a development certificate authority and install that CA’s root certificate on your device. That reduces the code you need to write and ensures there will be no security mishaps."


After searching online, I have not been able to find a tutorial, guide or instructions on how to call/use the device-level installed root certificate within an iOS app. Any links or code example would be greatly appreciated.


Thank You.

overcoming self-signed certificates? NSURLSession delegates not called.
 
 
Q