2

I am making a basic "hello world" iOS app. I have an ubuntu server in the cloud which I want to query from the iOS app. I understand that the server needs to be secure, ie, needs to be accessed via https request, and the certificate on the server needs to be "trusted".

Right now what I am trying to do is override this requirement. I have a self-signed certificate that is working for https on my server. When I make the request with the iOS app, it gives me some errors about NSURLErrorFailingURLPeerTrustErrorKey and even one line returned saying: NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?.

I know this is a common issue and there are many threads on this site about how to deal with it. I tried a piece of code from this post. I added this piece to my code:

    func URLSession(session: NSURLSession, task: NSURLSessionTask, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
        if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust{
            let credential = NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!)
            completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential,credential);
        }
    }

My entire ViewController code is here:

import UIKit

class ViewController: UIViewController, UITextFieldDelegate, NSURLSessionDelegate {
    // MARK: Properties
    @IBOutlet weak var nameTextField: UITextField!
    @IBOutlet weak var mealNameLabel: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Handle the text field’s user input through delegate callbacks.
        nameTextField.delegate = self
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    // MARK: UITextFieldDelegate
    func textFieldShouldReturn(textField: UITextField) -> Bool {
        // Hide the keyboard.
        textField.resignFirstResponder()
        return true
    }

    func textFieldDidEndEditing(textField: UITextField) {
        mealNameLabel.text = nameTextField.text
    }

    // MARK: Actions
    @IBAction func setDefaultLabelText(sender: UIButton) {
        mealNameLabel.text = "Default Text"
        post_request()
    }

    func post_request(){
        let request = NSMutableURLRequest(URL: NSURL(string: "https://54.164.XXX.XX/post_script.php")!)
        request.HTTPMethod = "POST"
        let postString = "id=13&name=Jack"
        request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)

        let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
            data, response, error in

            if error != nil {
                print("error=\(error)")
                return
            }

            print("response = \(response)")

            let responseString = NSString(data: data!, encoding: NSUTF8StringEncoding)
            print("responseString = \(responseString)")
        }
        task.resume()
    }

    func URLSession(session: NSURLSession, task: NSURLSessionTask, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
        if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust{
            let credential = NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!)
            completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential,credential);
        }
    }

}

It looks like the idea is to pick up the challenge event from the connection, and tell it that "yes I want to proceed". But that piece of code does not seem to be getting called. The post request gets sent, but I receive the error messages about the untrusted certificate. Can someone help me fix my code so that I can accept this certificate from my server?

Community
  • 1
  • 1
jeffery_the_wind
  • 17,048
  • 34
  • 98
  • 160

2 Answers2

2

You need to make your ViewController NSURLSessionDelegate to receive the callbacks and set the session's delegate to be your controller, like this:

let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: nil)
...
let task = session.dataTaskWithRequest(request) { data, response, error in

}

task.resume()
libec
  • 1,555
  • 1
  • 10
  • 19
  • @keithbhunter beat me to it. The app transport security is useful comment as well – libec Nov 12 '15 at 16:51
  • I see what you're saying here, I am trying to understand. But where in you answer does it show how to accept the certificate and get the response from the server? – jeffery_the_wind Nov 12 '15 at 16:52
  • I have created the session with your lines of code here, but still it refuses the connection because of the certificate – jeffery_the_wind Nov 12 '15 at 16:55
  • The part where you accept the certificate: `completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential,credential)`. Check the AppTransportSecurity like @keithbhunter said, this might block the app from working if the certificate is not valid – libec Nov 12 '15 at 16:56
1

You cannot use the shared session to get those delegate methods. You must instantiate a new NSURLSession and declare yourself as the delegate.

// Delegate methods won't be callend
let task = NSURLSession.sharedSession()...

// Use this to get delegate calls
let task = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), 
                        delegate: self, 
                        delegateQueue: /* make a queue */)...

With iOS 9, you will also need to look into NSAppTransportSecurity and adding those keys to your plist. These are needed in iOS 9 to allow SSL connections.

keithbhunter
  • 12,258
  • 4
  • 33
  • 58
  • I am still new understanding what delegates mean. What do you mean here in /* make a queue */. LIke is the context of my code, how could I just post data to my IP, accept the cert and get the response? – jeffery_the_wind Nov 12 '15 at 16:50
  • 1
    Delegate is an object that implements certain methods and an object will call. In this context, you say the method `func URLSession(_:task:didReceiveChallenge:completionHandler:) -> Void)` is not being called. That's because you are not the delegate. When you initialize that object and set yourself as the delegate, that method will get called and accept the cert as the code you have there does. The make a queue part means to make a background queue. Or if you're lazy, use `NSOperationQueue.mainQueue()`. – keithbhunter Nov 12 '15 at 16:53