EXC_BAD_ACCESS 在加载自己的 ca 证书时在 Swift 2.0 中调用 SecTrustEvaluate

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

我正在加载我自己的根证书作为我的应用程序的锚点证书,但我总是在 SecTrustEvaluate(trust!, &trustResult) 行上得到 EXC_BAD_ACCESS。 谁能看出我做错了什么?

我查看了 Always EXC_BAD_ACCESS on SecTrustEvaluate,但它没有解决我的问题,因为我认为我的问题可能是 Swift 特有的问题。

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)
    }
}

使用 Certificate and Public Key Pinning 作为指南,我想出了这个作为 Swift 中的证书固定实现。这与您的方法不同,但在我的测试中有效。

可以使用 Firefox 下载 .der 格式的证书,方法是访问网站,单击地址栏中的挂锁 > 更多信息 > 查看证书 > 详细信息选项卡 > 导出(select DER 从列表格式)。

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")
}

最终解决方案如下。我的初始代码的问题是我尝试创建 CFArray 的方式。代码中的某处:

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

,我丢失了对证书的引用。通过将代码更改为:

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

,问题已解决。

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)
    }