Skip to content

Instantly share code, notes, and snippets.

@sye8
Last active March 26, 2024 18:45
Show Gist options
  • Save sye8/32b064d2d11437c1463c5923fdf82daf to your computer and use it in GitHub Desktop.
Save sye8/32b064d2d11437c1463c5923fdf82daf to your computer and use it in GitHub Desktop.
Swift 3 Trusting a Self-Signed SSL Certificate

Swift 3 Trusting a Self-Signed SSL Certificate

For testing purposes, we may want to have local testing servers using self-signed SSL certificate for HTTPS connection. Now, suppose we have a local server with self-signed certificate, establishing an actual HTTPS connection would require us to trust our self-signed certificate. It is easy on a browser: few clicks, and you will be on your way. But how about a Swift application?

Caution: Get an actual, trusted, signed certicate for production apps!

0. A regular HTTPS request

App Transport Security (ATS) is a technology that requires an app to either support best practice HTTPS security or statically declare its security limitations via a property in its Info.plist.

Normally, a simple HTTP/HTTPS POST request in Swift 3 will look something like this:

func request(){
    let postString = "test"
    var request = URLRequest(url: URL(string: "https://google.com")!)
    request.httpMethod = "POST"
    request.httpBody = postString.data(using: String.Encoding.utf8)
    let task = URLSession.shared.dataTask(with: request){(data: Data?, response: URLResponse?, error: Error?) in
        if let error = error{
            print("error: ")
            print(error)
            return
        }
        if let data = data{
            print("data: ")
            print(data)
        }
        if let response = response{
            print("response: ")
            print(response)
        }
    }
    task.resume()
}

Notice that the URL string states explicity to use HTTPS. Here, Google is used as an example as it has a trusted certificate:

googlecert

And this is the response we get:

<NSHTTPURLResponse: 0x60c000031cc0> { URL: https://google.com/ } { Status Code: 405, Headers {
    "Content-Length" =     (
        1589
    );
    "Content-Type" =     (
        "text/html; charset=UTF-8"
    );
    Date =     (
        "Wed, 01 Aug 2018 09:40:51 GMT"
    );
    Server =     (
        gws
    );
    allow =     (
        "GET, HEAD"
    );
    "alt-svc" =     (
        "quic=\":443\"; ma=2592000; v=\"44,43,39,35\""
    );
    "x-frame-options" =     (
        SAMEORIGIN
    );
    "x-xss-protection" =     (
        "1; mode=block"
    );
} }

All is good.

In this case, ATS took care of our secure connection.

However, it will be a different story if we attempt an HTTPS connection to a server with self-signed certificate:

...(TL;DR)
Error Domain=NSURLErrorDomain Code=-1202 "The certificate for this server is invalid. You might be connecting to a server that is pretending to be “172.24.1.1” which could put your confidential information at risk." 
...(TL;DR)

Well, here is how we get Swift to trust us to be who we claim we are.

1. App Transport Security Exclusion

In order for our Swift app to trust our self-signed certificate, we would first need to exclude our domain (and its subdomains) from ATS. Open Info.plist as source code and insert the following:

<key>NSAppTransportSecurity</key>
	<dict>
		<key>NSExceptionDomains</key>
		<dict>
			<key>172:24:1:1</key>
			<dict>
				<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
				<true/>
				<key>NSIncludesSubdomains</key>
				<true/>
			</dict>
		</dict>
	</dict>

Which would look like this if Info.plist is opened as property list:

info

However, this is not enough. We would also need to handle authentication challenges in our code.

2. Handling Authentication Challenges

In order to handle a URLAuthenticationChallenge, we need to implement

func urlSession(URLSession, didReceive: URLAuthenticationChallenge, completionHandler: (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)

in a URLSessionDelegate.

For example, suppose we trigger our HTTP request in ViewController. We will extend it to be a URLSessionDelegate and implement the method, which white-lists our domain:

extension ViewController: URLSessionDelegate{
    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        if challenge.protectionSpace.host == "172.24.1.1" {
            completionHandler(.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!))
        } else {
            completionHandler(.performDefaultHandling, nil)
        }
    }
}

Now we would need to use our delegate, and so we will not use URLSession.shared.dataTask(with: request). What we need is a dataTask of a URLSession that uses our delegate:

func request(){
    let postString = "test"
    var request = URLRequest(url: URL(string: "https://172.24.1.1:8443")!)
    request.httpMethod = "POST"
    request.httpBody = postString.data(using: String.Encoding.utf8)
    let session = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: OperationQueue.main)
    let task = session.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
        if let error = error{
            print("error: ")
            print(error)
            return
        }
        if let data = data{
            print("data: ")
            print(data)
        }
        if let response = response{
            print("response: ")
            print(response)
        }
    }
    task.resume()
}

Now, try to make the POST request:

<NSHTTPURLResponse: 0x60c0000379a0> { URL: https://172.24.1.1:8443/ } { Status Code: 412, Headers {
    "Content-Language" =     (
        en
    );
    "Content-Length" =     (
        980
    );
    "Content-Type" =     (
        "text/html;charset=utf-8"
    );
    Date =     (
        "Wed, 01 Aug 2018 11:01:55 GMT"
    );
    Server =     (
        "Apache-Coyote/1.1"
    );
} }

Yay!

@yasodha-r
Copy link

Well explained. Thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment