Swift SSL error with self signed certificate

49,504

Solution 1

@Ashish Kakkad was spot on. This works:

class Blah: NSURLSessionDelegate {

    func rest() {
        let path = "https://localhost:8443/greeting"
        let request = NSMutableURLRequest(URL: NSURL(string: path)!)
        let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
        let session = NSURLSession(configuration: configuration, delegate: self, delegateQueue:NSOperationQueue.mainQueue())
        let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
            let json:JSON = JSON(data: data!)
            if let c = json["content"].string {
                print(c)
            }
        })
        task.resume()
    }

    func URLSession(session: NSURLSession, task: NSURLSessionTask, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
        completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential, NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!))
    }
}

With this in the Info.plist file:

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSExceptionDomains</key>
    <dict>
        <key>localhost</key>
        <dict>
            <key>NSExceptionAllowsInsecureHTTPLoads</key>
            <true/>
        </dict>
    </dict>
</dict>

Solution 2

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.

Solution 3

you can use your own certificates instead of my certificates(fullchain.pem)

    class AccessingServer: NSObject,URLSessionDelegate {

    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {

        if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
            // First load our extra root-CAs to be trusted from the app bundle.
            let trust = challenge.protectionSpace.serverTrust

            let rootCa = "fullchain"
            if let rootCaPath = Bundle.main.path(forResource: rootCa, ofType: "pem") {
                if let rootCaData = NSData(contentsOfFile: rootCaPath) {

                    let rootCert = SecCertificateCreateWithData(nil, rootCaData)!

                    SecTrustSetAnchorCertificates(trust!, [rootCert] as CFArray)

                    SecTrustSetAnchorCertificatesOnly(trust!, false)
                }
            }

            var trustResult: SecTrustResultType = SecTrustResultType.invalid
            SecTrustEvaluate(trust!, &trustResult)

            if (trustResult == SecTrustResultType.unspecified ||
                trustResult == SecTrustResultType.proceed) {
                // Trust certificate.

                let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
                challenge.sender?.use(credential, for: challenge)

            } else {
                NSLog("Invalid server certificate.")
                challenge.sender?.cancel(challenge)
            }
        } else {
            NSLog("Got unexpected authentication method \(challenge.protectionSpace.authenticationMethod)");
            challenge.sender?.cancel(challenge)
        }
    }

   }
Share:
49,504
Marcus Leon
Author by

Marcus Leon

Director Clearing Technology, Intercontinental Exchange. Develop the clearing systems that power ICE/NYSE's derivatives markets.

Updated on July 09, 2022

Comments

  • Marcus Leon
    Marcus Leon almost 2 years

    This code attempts and fails to access an SSL URL which works in a browser:

    let path = "https://localhost:8443/greeting"
    let request = NSMutableURLRequest(URL: NSURL(string: path)!)
    let session = NSURLSession.sharedSession()
    
    let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
        let json:JSON = JSON(data: data!)
        if let c = json["content"].string {
            print(c)
        }
    })
    task.resume()
    

    Fails with the error:

    Optional(Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSURLErrorFailingURLPeerTrustErrorKey=,

    What is required to allow the app to accept this cert?

    The certificate in question is self signed. Read a few solutions on SO without success.

    Running Xcode 7.2

  • midori
    midori over 7 years
    This works, but is only a workaround. Also please notice that Exceptions made with ATS soon will be deprecated.
  • Relsell
    Relsell about 7 years
    I also got news from fellow devs .. that apple doesn't allow apps in app store with this setting on.
  • drewster
    drewster almost 6 years
    This solution simply ignores the certificate altogether, rather than trying to confirm the user is using the self-signed certificate.
  • Aaron Bratcher
    Aaron Bratcher over 5 years
    What if I want to simply allow ALL certs while in the simulator? Is there something I can add in my code when compiling for it?
  • drewster
    drewster over 5 years
    Just stick "completionHandler(.useCredential, credential)" after the first two let statements ("let trust ...", and "let credential..."), and it will accept everything.
  • drewster
    drewster over 5 years
    Usually that's because the cert isn't in DER format. See the openssl commands above.
  • Rajesh Maurya
    Rajesh Maurya almost 5 years
    From where to get the certificate?
  • Sai kumar Reddy
    Sai kumar Reddy almost 5 years
    #Rajesh certificates are provided by the backend team
  • Rajesh Maurya
    Rajesh Maurya almost 5 years
    I exported the certificate from keychain. I got crash at this line SecTrustSetAnchorCertificates(trust!, rootCert as! CFArray).
  • xarly
    xarly almost 4 years
    To avoid getting null from SecTrustSetAnchorCertificates : stackoverflow.com/questions/40155587/…
  • Karlicic Bojan
    Karlicic Bojan almost 4 years
    This is not a solution. By doing it this way you are not protected for MITM attack. You are approving all certs, without checking if the cert is valid.
  • Nick
    Nick almost 3 years
    This worked first time. Why isn't it the accepted answer?