Howto add certificate pinning to a NSURLSession in Swift?
The OWASP website contains only an example for Objective-C and NSURLConnection.
SSL Pinning is a technique that we use on the client-side to avoid a man-in-the-middle attack by validating the server certificates. The developers embed (or pin) a list of trustful certificates to the client application during development, and use them to compare against the server certificates during runtime.
SSL Pinning is a technique used in swift to prevent man-in-middle attacks. In this process, the app validates the Server's certificate again after the SSL handshaking. There is a local copy of trustful certificates maintained at the client's end and compare them with the Server's certificates at runtime.
The SSL pinning (or public key, or certificate pinning) is a technique mitigating Man-in-the-middle attacks against the secure HTTPS communication. The typical Android solution is to bundle the hash of the certificate, or the exact data of the certificate into the application.
SSL pinning allows you to verify the server's identity on top of the SSL chain of trust verification. With SSL pinning, you can refuse all connections except the ones with the designated server whose SSL certificate we've saved into our local bundle.
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")
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With