Stephan
Stephan

Reputation: 881

EXC_BAD_ACCESS on SecTrustEvaluate call in Swift 2.0 when loading own ca cert

I'm loading my own root cert as an anchor cert for my app, but I'm always getting a EXC_BAD_ACCESS on the SecTrustEvaluate(trust!, &trustResult) line. Can anyone see what I'm doing wrong?

I've looked at Always EXC_BAD_ACCESS on SecTrustEvaluate, but it does not solve my problem as I think mine might be a Swift-specific issue.

public func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
    if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
        // First load our extra root-CA to be trusted from the app bundle.
        let trust = challenge.protectionSpace.serverTrust

        let rootCa = "SSLcomDVCA_2_DER"
        if let rootCaPath = NSBundle.mainBundle().pathForResource(rootCa, ofType: "cer") {
            if let rootCaData = NSData(contentsOfFile: rootCaPath) {

                let cfData = CFDataCreate(kCFAllocatorDefault, UnsafePointer<UInt8>(rootCaData.bytes), rootCaData.length)

                let rootCert = SecCertificateCreateWithData(nil, cfData)

                let certs: [CFTypeRef] = [rootCert!]
                let certPointer = UnsafeMutablePointer<UnsafePointer<Void>>(certs)
                let certArrayRef = CFArrayCreate(nil, certPointer
                    , certs.count, nil)
                SecTrustSetAnchorCertificates(trust!, certArrayRef)
                SecTrustSetAnchorCertificatesOnly(trust!, false) // also allow regular CAs.
            }
        }

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

        if (Int(trustResult) == kSecTrustResultUnspecified ||
            Int(trustResult) == kSecTrustResultProceed) {
                // Trust certificate.
                let credential = NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!)
                challenge.sender!.useCredential(credential, forAuthenticationChallenge: challenge)
        } else {
            NSLog("Invalid server certificate.")
            challenge.sender!.cancelAuthenticationChallenge(challenge)
        }
    } else {
        NSLog("Got unexpected authentication method \(challenge.protectionSpace.authenticationMethod)");
        challenge.sender!.cancelAuthenticationChallenge(challenge)
    }
}

Upvotes: 0

Views: 858

Answers (2)

Stephan
Stephan

Reputation: 881

The final solution is below. The issue with my initial code was the way I tried to create the CFArray. Somewhere in the code:

let certs: [CFTypeRef] = [rootCert!]
let certPointer = UnsafeMutablePointer<UnsafePointer<Void>>(certs)
let certArrayRef = CFArrayCreate(nil, certPointer, certs.count, nil)

, I lost reference to the certificate. By changing the code to:

let certs: [CFTypeRef] = [rootCert as! CFTypeRef]  
let certArrayRef : CFArrayRef = CFBridgingRetain(certs as NSArray) as! CFArrayRef

, the issue was sorted.

func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
    if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {

        let trust = challenge.protectionSpace.serverTrust

        let rootCa = "SSLcomDVCA_2_DER2"
        if let rootCaPath = NSBundle.mainBundle().pathForResource(rootCa, ofType: "der") {
            if let rootCaData: NSData = NSData(contentsOfFile: rootCaPath) {

                let cfData = CFDataCreate(kCFAllocatorDefault, UnsafePointer<UInt8>(rootCaData.bytes), rootCaData.length)

                let rootCert = SecCertificateCreateWithData(kCFAllocatorDefault, cfData)

                let certs: [CFTypeRef] = [rootCert as! CFTypeRef]

                let certArrayRef : CFArrayRef = CFBridgingRetain(certs as NSArray) as! CFArrayRef
                SecTrustSetAnchorCertificates(trust!, certArrayRef)

                SecTrustSetAnchorCertificatesOnly(trust!, false) // also allow regular CAs.
            }
        }

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

        if (Int(trustResult) == kSecTrustResultUnspecified ||
            Int(trustResult) == kSecTrustResultProceed) {
                // Trust certificate.
                let credential = NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!)
                challenge.sender!.useCredential(credential, forAuthenticationChallenge: challenge)
        } else {
            NSLog("Invalid server certificate.")
            challenge.sender!.cancelAuthenticationChallenge(challenge)
        }
    } else {
        NSLog("Got unexpected authentication method \(challenge.protectionSpace.authenticationMethod)");
        challenge.sender!.cancelAuthenticationChallenge(challenge)
    }

Upvotes: 0

paulvs
paulvs

Reputation: 12053

Using Certificate and Public Key Pinning as a guide, I came up with this as a certificate pinning implementation in Swift. It's different to your method, but it works in my tests.

Certificates in .der format can be downloaded using Firefox by going to the website, clicking the padlock in the address bar > More Information > View Certificate > Details tab > Export (select DER from the list of formats).

func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {

    if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {

        guard let serverTrust = challenge.protectionSpace.serverTrust else {
            print("serverTrust is nil")
            return
        }

        guard errSecSuccess == SecTrustEvaluate(serverTrust, nil) else {
            print("SecTrustEvaluate is not errSecSuccess")
            return
        }

        guard let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) else {
            print("serverCertificate is nil")
            return
        }

        let serverCertificateData = SecCertificateCopyData(serverCertificate)
        let data = CFDataGetBytePtr(serverCertificateData)
        let size = CFDataGetLength(serverCertificateData)
        let dataPtr = unsafeBitCast(data, UnsafeMutablePointer<Void>.self)

        let cert1 = NSData(bytes: dataPtr, length: size)

        guard let file = NSBundle.mainBundle().pathForResource("facebook.com", ofType: "der"), cert2 = NSData(contentsOfFile: file) else {
            print("Failed to open .der file")
            return
        }

        guard cert1 == cert2 else {
            print("Certificate pinning failed, certs unequal")
            return
        }

        // Good exit point. 
        print("PASSED Cert Pinning")
        return challenge.sender!.useCredential(NSURLCredential(forTrust: serverTrust), forAuthenticationChallenge: challenge)
    }

    print("FAILED Cert Pinning, authenticationMethod not ServerTrust")
}

Upvotes: 1

Related Questions