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