Невозможно общаться через HTTPS в приложении iOS с включенной ATS и собственным корневым центром сертификации

Мы пытаемся создать приложение, которое будет взаимодействовать с несколькими серверами по протоколу HTTPS с использованием NSURLSession, поскольку мы используем наш собственный корневой центр сертификации, который входит в наше приложение, и ATS полностью включен (для NSAllowsArbitraryLoads установлено значение False).

При вызове делегата аутентификации NSURLSession т.е. "URLAuthenticationChallenge" мы устанавливаем сертификат привязки через "SecTrustSetAnchorCertificates", который проверяет сертификат, проверяя его подпись плюс подписи сертификатов в его цепочке сертификатов, вплоть до сертификата привязки. Также используется SecTrustSetAnchorCertificatesOnly для исключения других якорей.

После успешного выполнения SecTrustEvaluate, получено сообщение об ошибке в SessionDataTask завершении Handler, которое упоминается ниже:

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

Замечания:

Когда сертификат Root CA установлен и доверен на устройстве, HTTPS-связь с включенной ATS работает без каких-либо ошибок. Но я не хочу, чтобы пользователь вручную устанавливал и доверял Root 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)
  }
}

1 ответ

Я думаю, что настоящий криптографический код выглядит правильно, по крайней мере, с первого взгляда, но сторона NSURLSession не работает правильно.

Несколько вопросов:

  • Всегда вызывать что-либо на challenge.sender через NSURLSession. Вот как вы делаете это для 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);
    
Другие вопросы по тегам