Sai Reddy's solution allows you to accept your self-signed certificate if it has a complete chain, but it also accepts others.
Marcus Leon's solution is a complete override -- basically ignoring all certificates.
I like this one better.
Swift 4.1, iOS 11.4.1
First, in your Info.plist:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
Second, wherever you use your NSURLSession, instead of setting up with URLSession.shared, use something like this:
session = URLSession(configuration: .default, delegate: APIURLSessionTaskDelegate(isSSLPinningEnabled: isSSLPinningEnabled), delegateQueue: nil)
Then add this class to handle pinning:
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
print("*** received SESSION challenge...\(challenge)")
let trust = challenge.protectionSpace.serverTrust!
let credential = URLCredential(trust: trust)
guard isSSLPinningEnabled else {
print("*** SSL Pinning Disabled -- Using default handling.")
completionHandler(.useCredential, credential)
return
}
let myCertName = "my_certificate_to_pin"
var remoteCertMatchesPinnedCert = false
if let myCertPath = Bundle.main.path(forResource: myCertName, ofType: "der") {
if let pinnedCertData = NSData(contentsOfFile: myCertPath) {
// Compare certificate data
let remoteCertData: NSData = SecCertificateCopyData(SecTrustGetCertificateAtIndex(trust, 0)!)
if remoteCertData.isEqual(to: pinnedCertData as Data) {
print("*** CERTIFICATE DATA MATCHES")
remoteCertMatchesPinnedCert = true
}
else {
print("*** MISMATCH IN CERT DATA.... :(")
}
} else {
print("*** Couldn't read pinning certificate data")
}
} else {
print("*** Couldn't load pinning certificate!")
}
if remoteCertMatchesPinnedCert {
print("*** TRUSTING CERTIFICATE")
completionHandler(.useCredential, credential)
} else {
print("NOT TRUSTING CERTIFICATE")
completionHandler(.rejectProtectionSpace, nil)
}
}
}
This class checks to see if you enabled certificate pinning. If you did, it completely ignores the normal certificate validation and does an exact comparison with the cert we include in the app. In this way, it only accepts your self-signed cert, and nothing else.
This solution requires that you put a "my_certificate_to_pin.der" file in your project, in your Resources folder. If you don't already have a Resources folder, just add one.
That certificate should be in DER format.
To create a self-signed certificate for your server, you would normally do something like this:
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout mycert.key -out mycert.cer
That generates two files -- a mycert.key private key file, and a mycert.cer -- the certificate itself. These are both in the X509 format. For iOS, you will need the cert in DER format, so do this:
openssl x509 -outform der -in mycert.cer -out my_certificate_to_pin.der
That generates the file you need on iOS.