Невозможно общаться через 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);