首页 > 解决方案 > 在启用 ATS 且捆绑了自己的根 CA 的情况下,无法在 iOS 应用程序中通过 HTTPS 进行通信

问题描述

我们正在尝试制作一个应用程序,该应用程序将使用 NSURLSession 通过 HTTPS 与我们的多个服务器进行通信,因为我们使用的是我们自己的根 CA,它捆绑在我们的应用程序中,并且 ATS 完全启用(NSAllowsArbitraryLoads 设置为 False)。

在调用 NSURLSession 身份验证委托即“URLAuthenticationChallenge”时,我们通过“SecTrustSetAnchorCertificates”设置锚证书,该​​证书通过验证证书的签名以及证书链中证书的签名来验证证书,直到锚证书。还使用 SecTrustSetAnchorCertificatesOnly 排除其他锚点。

成功执行 SecTrustEvaluate 后,在 SessionDataTask completionHandler 中收到错误,如下所述:

[4704:1445043] ATS failed system trust
[4704:1445043] System Trust failed for [1:0x1c41678c0]
[4704:1445043] TIC SSL Trust Error [1:0x1c41678c0]: 3:0
[4704:1445043] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9802)
(Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." 

笔记:

在设备上安装并信任根 CA 证书后,启用 ATS 的 HTTPS 通信可以正常工作,不会出现任何错误。但我不希望用户手动安装和信任设备上的根 CA。

代码片段:

func getRequest(){  
  let requestUrl = “https://server.test.com/hi.php” //url modified
        let configuration = URLSessionConfiguration.default  
        var request = try! URLRequest(url: requestUrl, method: .get)  

        let session = URLSession(configuration: configuration,  
                                 delegate: self,  
                                 delegateQueue: OperationQueue.main)  
        let task = session.dataTask(with: request, completionHandler: { (data, response, error) in  
            print("Data = \(data)")  
            print("Response = \(response)")  
            print("Error = \(error)")  
        })  
          task.resume()  
    }

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

if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {

    let trust = challenge.protectionSpace.serverTrust
    let rootCa = “root"
    if let rootCaPath = NSBundle.mainBundle().pathForResource(rootCa, ofType: "der") {
      if let rootCaData = NSData(contentsOfFile: rootCaPath) {
        let rootCert = SecCertificateCreateWithData(nil, rootCaData).takeRetainedValue()
        SecTrustSetAnchorCertificates(trust, [rootCert])
        SecTrustSetAnchorCertificatesOnly(trust, true) 
    }

    var trustResult: SecTrustResultType = 0
    SecTrustEvaluate(trust, &trustResult)
    if (Int(trustResult) == kSecTrustResultUnspecified ||
      Int(trustResult) == kSecTrustResultProceed) {
        // Trust certificate.
    } else {
      NSLog("Invalid server certificate.")
    }  
  } else {
    challenge.sender.cancelAuthenticationChallenge(challenge)
  }
}

标签: ioshttpsnsurlsessionroot-certificate

解决方案


我认为实际的加密代码看起来是正确的,至少一目了然,但是事情的 NSURLSession 方面并没有做正确的事情。

几个问题:

  • 使用 NSURLSession 在 challenge.sender 上调用任何东西总是错误的。这就是你为 NSURLConnection 做的方式。相反,您必须通过使用适当的处置(在某些情况下,还有凭据)调用提供的回调方法来指定行为。
  • 取消您不想明确处理的挑战是错误的。如果您取消挑战,您的意思是连接失败比继续连接更好。通常,您应该改用默认处理,以便那些其他类型的挑战在发生时不会导致连接失败。因此,在else最后的情况下,您应该执行以下操作:

    completionHandler(URLSession.AuthChallengeDisposition.performDefaultHandling, nil)
    

    如果你不这样做,你会破坏代理身份验证之类的东西,它也可能会在其他情况下破坏。

  • 当您获得无效证书时,您应该取消请求。你在哪里:

    NSLog("Invalid server certificate.")
    

    添加类似的内容:

    completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
    
  • 当您成功验证它时,您应该接受它。你在哪里 // 信任证书

    添加类似的内容:

    let credential = NSURLCredential(forTrust: challenge.protectionSpace.serverTrust)
    completionHandler(URLSession.AuthChallengeDisposition.useCredential, credential);
    

推荐阅读