iosswiftsslowasppinning

iOS certificate pinning with Swift and NSURLSession


Howto add certificate pinning to a NSURLSession in Swift?

The OWASP website contains only an example for Objective-C and NSURLConnection.


Solution

  • Swift 3+ Update:

    Just define a delegate class for NSURLSessionDelegate and implement the didReceiveChallenge function (this code is adapted from the objective-c OWASP example):

    class NSURLSessionPinningDelegate: NSObject, URLSessionDelegate {
    
        func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void) {
    
            // Adapted from OWASP https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning#iOS
    
            if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) {
                if let serverTrust = challenge.protectionSpace.serverTrust {
                    let isServerTrusted = SecTrustEvaluateWithError(serverTrust, nil)
    
                    if(isServerTrusted) {
                        if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) {
                            let serverCertificateData = SecCertificateCopyData(serverCertificate)
                            let data = CFDataGetBytePtr(serverCertificateData);
                            let size = CFDataGetLength(serverCertificateData);
                            let cert1 = NSData(bytes: data, length: size)
                            let file_der = Bundle.main.path(forResource: "certificateFile", ofType: "der")
    
                            if let file = file_der {
                                if let cert2 = NSData(contentsOfFile: file) {
                                    if cert1.isEqual(to: cert2 as Data) {
                                        completionHandler(URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust:serverTrust))
                                        return
                                    }
                                }
                            }
                        }
                    }
                }
            }
    
            // Pinning failed
            completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
        }
    
    }
    

    (you can find a Gist for Swift 2 here - from the initial answer)

    Then create the .der file for your website using openssl

    openssl s_client -connect my-https-website.com:443 -showcerts < /dev/null | openssl x509 -outform DER > my-https-website.der
    

    and add it to the xcode project. Double check that it's present in the Build phases tab, inside the Copy Bundle Resources list. Otherwise drag and drop it inside this list.

    Finally use it in your code to make URL requests:

    if let url = NSURL(string: "https://my-https-website.com") {
    
        let session = URLSession(
                configuration: URLSessionConfiguration.ephemeral,
                delegate: NSURLSessionPinningDelegate(),
                delegateQueue: nil)
    
    
        let task = session.dataTask(with: url as URL, completionHandler: { (data, response, error) -> Void in
            if error != nil {
                print("error: \(error!.localizedDescription): \(error!)")
            } else if data != nil {
                if let str = NSString(data: data!, encoding: String.Encoding.utf8.rawValue) {
                    print("Received data:\n\(str)")
                } else {
                    print("Unable to convert data to text")
                }
            }
        })
    
        task.resume()
    } else {
        print("Unable to create NSURL")
    }