2017/09/04

Requests to server with self signed certificate in iOS

This is kind of old topic but information seems to be fussy to me, I couldn't find all the answers in a a single place so here are my notes.... Let's say we have a server which uses https but with a self signed certificate (オレオレ証明書), and your iOS app needs to do requests to it. Since iOS 8, if I well remember, iOS apps don't work with these servers without additional settings.
Official info from Apple suggest creation of a certificate which means changes in server side which is not an option in my case.
Below I setup my app so requests can be done with an insecure certificate only to my server. This should be done only for development/testing purposes and should not be in production

1. Instruct ATS to ignore your server

Edit Info.plist to have this (From Lory Lory in Stackoverflow):
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSExceptionDomains</key>
    <dict>
        <key>self-signed-certificate.server.com</key>
        <dict>
            <!--Include to allow subdomains too-->
            <key>NSIncludesSubdomains</key>
            <true/>
            <!--Include to allow ssl requests with self signed certificates and other insecure stuff-->
            <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
            <true/>
        </dict>
    </dict>
</dict>
Official (and poor) documentation regarding NSAppTransportSecurity can be found in InfoPlistKeyReference - CocoaKeys. Beware that key names are not even correct, see NSExceptionAllowsInsecureHTTPLoads, instead of NSTemporaryExceptionAllowsInsecureHTTPLoads, etc.

2. Make sure challenge is fulfilled in the WKWebView

If you are using WKWebView then you need to implement the navigation delegate. Remember, at least you should have a naive filter like below. Accepting everything is much more dangerous. This should be done only for testing/developing purposes.
func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void) {
    if challenge.protectionSpace.host == "self-signed-certificate.server.com" {
        let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
        completionHandler(.useCredential, credential)
    } else {
        completionHandler(.performDefaultHandling, nil)
    }
}
Now do setup the webview and do the request, everything should be ready for testing/developing now :)
webView = WKWebView()
webView.frame = CGRect(x: 0, y: 0, width: w, height: h)
webView.navigationDelegate = self

let request = URLRequest(url: URL(string: "https://self-signed-certificate.server.com")!)
webView.load(request)

3. Make sure challenge is fulfilled in the NSURLSession

If you are using the standard NSURLSession APIs then implement NSURLSessionDelegate method as below:
func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
     // Pass test server with self signed certificate
    if challenge.protectionSpace.host == "self-signed-certificate.server.com" {
        completionHandler(.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!))
    } else {
        completionHandler(.performDefaultHandling, nil)
    }
}
Now do the call (do not forget to set the delegate to your session!):
let session = NSURLSession(
      configuration: NSURLSessionConfiguration.default,
      delegate: object, // DO NOT FORGET YOUR OBJECT HERE!!
      delegateQueue: nil)
let url = URL(string: "https://self-signed-certificate.server.com")!
let task = session.dataTask(with: url) { (data, response, error) in
    ...
}
task.resume()

3. Make sure challenge is fulfilled in Alamofire

If you are using Alamofire (one famous library wrapper for NSURLSession + some goodies) then implement something very similar as above.
let sessionManager = SessionManager()
sessionManager.delegate.taskDidReceiveChallengeWithCompletion = { (_, _, challenge, completionHandler) -> Void in
    // Pass test server with self signed certificate
    if challenge.protectionSpace.host == "self-signed-certificate.server.com" {
        completionHandler(.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!))
    } else {
        completionHandler(.performDefaultHandling, nil)
    }
}
Now simply do the call (Alamofire example)
sessionManager.request("https://self-signed-certificate.server.com", method: .get).response { response in 
 ...
}

0 comments :