【问题标题】:Swift SSL error with self signed certificate带有自签名证书的 Swift SSL 错误
【发布时间】:2016-06-06 04:15:25
【问题描述】:

此代码尝试访问在浏览器中工作的 SSL URL,但失败:

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

因错误而失败:

可选(错误域=NSURLErrorDomain 代码=-1200“SSL 错误有 发生并且无法与服务器建立安全连接。” UserInfo={NSURLErrorFailingURLPeerTrustErrorKey=,

需要什么条件才能让应用接受此证书?

有问题的证书是自签名的。阅读一些关于 SO 的解决方案,但没有成功。

运行 Xcode 7.2

【问题讨论】:

标签: ios xcode swift ssl


【解决方案1】:

@Ashish Kakkad 出现在现场。这有效:

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

在 Info.plist 文件中有这个:

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

【讨论】:

  • 这可行,但只是一种解决方法。另请注意,不久将弃用 ATS 的例外情况。
  • 我还从其他开发者那里得到消息 .. 说苹果不允许应用商店中的应用打开此设置。
  • 此解决方案完全忽略证书,而不是尝试确认用户正在使用自签名证书。
  • 这不是解决方案。通过这种方式,您不会受到 MITM 攻击的保护。您正在批准所有证书,而不检查证书是否有效。
【解决方案2】:

您可以使用自己的证书代替我的证书(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)
        }
    }

   }

【讨论】:

  • 从哪里获得证书?
  • #Rajesh 证书由后端团队提供
  • 我从钥匙串导出了证书。我在这条线 SecTrustSetAnchorCertificates(trust!, rootCert as!CFArray) 崩溃了。
  • 为了避免从 SecTrustSetAnchorCertificates 获得空值:stackoverflow.com/questions/40155587/…
【解决方案3】:

Sai Reddy 的解决方案允许您接受具有完整链的自签名证书,但它也接受其他证书。

Marcus Leon 的解决方案是完全覆盖——基本上忽略所有证书。

我更喜欢这个。

Swift 4.1、iOS 11.4.1

首先,在您的 Info.plist 中:

<key>NSAppTransportSecurity</key>
    <dict>
        <key>NSAllowsArbitraryLoads</key>
        <true/>
    </dict>

其次,无论您在何处使用 NSURLSession,而不是使用 URLSession.shared 进行设置,请使用以下内容:

session = URLSession(configuration: .default, delegate: APIURLSessionTaskDelegate(isSSLPinningEnabled: isSSLPinningEnabled), delegateQueue: nil)

然后添加这个类来处理固定:

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

此类检查您是否启用了证书固定。如果你这样做了,它会完全忽略正常的证书验证,并与我们在应用程序中包含的证书进行精确比较。这样,它接受您的自签名证书,而不接受其他任何东西。

此解决方案要求您将“my_certificate_to_pin.der”文件放入项目的 Resources 文件夹中。如果您还没有 Resources 文件夹,只需添加一个即可。

该证书应为 DER 格式。

要为您的服务器创建自签名证书,您通常会执行以下操作:

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout mycert.key -out mycert.cer

这会生成两个文件——一个mycert.key私钥文件和一个mycert.cer——证书本身。这些都是 X509 格式。对于 iOS,您将需要 DER 格式的证书,请执行以下操作:

openssl x509 -outform der -in mycert.cer -out my_certificate_to_pin.der

这会生成您在 iOS 上需要的文件。

【讨论】:

  • 如果我只想在模拟器中允许所有证书怎么办?编译时我可以在代码中添加什么内容吗?
  • 只需在前两个 let 语句(“let trust ...”和“let credential...”)之后粘贴“completionHandler(.useCredential, credential)”,它就会接受所有内容。
  • 通常这是因为证书不是 DER 格式。请参阅上面的 openssl 命令。
  • 这是第一次。为什么它不是公认的答案?
猜你喜欢
  • 1970-01-01
  • 2015-06-18
  • 2015-03-29
  • 2019-10-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-12
相关资源
最近更新 更多