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

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

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)
    }
}

3. Make sure challenge is fulfilled in Alamofire

Alamofire internally uses NSURLSession so implement something very similar as above is ok.
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 :