简体   繁体   English

无法在启用了ATS且捆绑了自己的根CA的iOS App中通过HTTPS通信

[英]Unable to communicate over HTTPS in iOS App with ATS enabled and Own Root CA bundled

We are trying to make an app which will communicate with our multiple servers over HTTPS using NSURLSession, in that we are using our own Root CA which is bundled in our app and ATS fully enabled (NSAllowsArbitraryLoads set to False). 我们正在尝试制作一个可以使用NSURLSession通过HTTPS与多个服务器通信的应用程序,因为我们使用了自己的根CA,该根CA捆绑在我们的应用程序中并且完全启用了ATS(NSAllowsArbitraryLoads设置为False)。

On call of NSURLSession Authentication delegate ie “URLAuthenticationChallenge” we are setting anchor cert through “SecTrustSetAnchorCertificates” which validates a certificate by verifying its signature plus the signatures of the certificates in its certificate chain, up to the anchor certificate. 在调用NSURLSession身份验证委托(即“ URLAuthenticationChallenge”)时,我们通过“ SecTrustSetAnchorCertificates”设置锚证书,该​​证书通过验证其签名以及证书链中证书的签名(直至锚证书)来验证证书。 Also used SecTrustSetAnchorCertificatesOnly to exclude other anchors. 还使用SecTrustSetAnchorCertificatesOnly排除其他锚点。

After successful execution of SecTrustEvaluate, received an error in SessionDataTask completionHandler which is mentioned below: 成功执行SecTrustEvaluate之后,在SessionDataTaskcompleteHandler中收到错误,如下所述:

[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." 

Note: 注意:

When Root CA Certificate is installed and trusted on the device then HTTPS communication with ATS enabled works without any error. 在设备上安装并信任根CA证书后,启用ATS的HTTPS通信即可正常工作。 But I don't want user to manually install and trust the Root CA on the device. 但是我不希望用户在设备上手动安装并信任根CA。

Code Snippet: 代码段:

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

I think the actual crypto code looks right, at least at a glance, but the NSURLSession side of things isn't doing the right thing. 我认为实际的加密代码至少乍看之下是正确的,但是NSURLSession方面并没有做正确的事情。

Several issues: 几个问题:

  • It is always a mistake to call anything on challenge.sender with NSURLSession. 用NSURLSession调用Challenge.sender上的任何东西总是错误的。 That's the way you do it for NSURLConnection. 这就是您为NSURLConnection执行的方式。 Instead, you must specify a behavior by calling the provided callback method with an appropriate disposition (and, in some cases, a credential). 相反,您必须通过以适当的配置(在某些情况下还包括凭据)调用提供的回调方法来指定行为。
  • It is a mistake to cancel challenges that you don't want to explicitly handle. 取消不想明确处理的挑战是错误的。 If you cancel a challenge, you're saying that it is better for the connection fail than to continue. 如果您取消挑战,则表示连接失败比继续失败更好。 Typically, you should use default handling instead so that those other types of challenges don't cause connection failures when they occur. 通常,您应该改用默认处理,以便那些其他类型的挑战在发生连接时不会引起连接失败。 So in the else case at the end, you should be doing something like: 因此,在else情况下,您应该执行以下操作:

     completionHandler(URLSession.AuthChallengeDisposition.performDefaultHandling, nil) 

    If you don't do this, you will break things like proxy authentication, and it will probably break in other situations as well. 如果不这样做,则会破坏代理身份验证之类的功能,并且在其他情况下也可能会中断。

  • You should cancel the request when you get an invalid cert. 收到无效证书后,您应该取消请求。 Where you have: 您在哪里:

     NSLog("Invalid server certificate.") 

    add something like: 添加类似:

     completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil) 
  • You should accept the credential when you successfully validate it. 成功验证证书后,您应该接受该证书。 Where you have // Trust certificate 您拥有//信任证书的地方

    add something like: 添加类似:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM