Swift 3 UrlSession с аутентификацией клиента по сертификату
Я использую URLSession, чтобы сделать запрос get с протоколом TLS 1.2 и сертификатами (которые являются самозаверяющими), включенными в основной комплект. Мне удалось выполнить закрепление, но серверу также требуется сертификат клиента для проверки подлинности, поэтому я пытаюсь ответить на AuthenticationChallenge с помощью UrlCredential, но он не работает: я продолжаю получать NSURLErrorDomain Code=-1206, который означает "Сервер" my_server_domain.it " требуется сертификат клиента."
Вот моя просьба:
func makeGetRequest(){
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()
}
URLSessionDelegate, где я отвечаю на AuthenticationChallenge:
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
let authenticationMethod = challenge.protectionSpace.authenticationMethod
print("authenticationMethod=\(authenticationMethod)")
if authenticationMethod == NSURLAuthenticationMethodClientCertificate {
completionHandler(.useCredential, getClientUrlCredential())
} else if authenticationMethod == NSURLAuthenticationMethodServerTrust {
let serverCredential = getServerUrlCredential(protectionSpace: challenge.protectionSpace)
guard serverCredential != nil else {
completionHandler(.cancelAuthenticationChallenge, nil)
return
}
completionHandler(.useCredential, serverCredential)
}
}
Закрепление сертификата сервера:
func getServerUrlCredential(protectionSpace:URLProtectionSpace)->URLCredential?{
if let serverTrust = protectionSpace.serverTrust {
//Check if is valid
var result = SecTrustResultType.invalid
let status = SecTrustEvaluate(serverTrust, &result)
print("SecTrustEvaluate res = \(result.rawValue)")
if(status == errSecSuccess),
let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) {
//Get Server Certificate Data
let serverCertificateData = SecCertificateCopyData(serverCertificate)
//Get Local Certificate NSData
let localServerCertNSData = certificateHelper.getCertificateNSData(withName: "localServerCertName", andExtension: "cer")
//Check if certificates are equals, otherwhise pinning failed and return nil
guard serverCertificateData == localServerCertNSData else{
print("Certificates doesn't match.")
return nil
}
//Certificates does match, so we can trust the server
return URLCredential(trust: serverTrust)
}
}
return nil
}
И вот где я получаю URLCredential клиента из сертификата PKCS12 (.pfx):
func getClientUrlCredential()->URLCredential {
let userCertificate = certificateHelper.getCertificateNSData(withName: "certificate",
andExtension: "pfx")
let userIdentityAndTrust = certificateHelper.extractIdentityAndTrust(fromCertificateData: userCertificate, certPassword: "cert_psw")
//Create URLCredential
let urlCredential = URLCredential(identity: userIdentityAndTrust.identityRef,
certificates: userIdentityAndTrust.certArray as [AnyObject],
persistence: URLCredential.Persistence.permanent)
return urlCredential
}
Обратите внимание, что func 'extractIdentityAndTrust' -successfully- возвращает структуру с указателями на идентификатор, цепочку сертификатов и доверие, извлеченные из PKCS12; Я знаю, что идентификационные данные и сертификаты должны храниться в цепочке для ключей, но на данный момент я просто включаю их в комплект, главным образом потому, что документация для цепочки для ключей совсем не хороша.
Я также добавил параметры безопасности транспорта приложения в свой файл Info.plist следующим образом.
Похоже, клиент даже не пытается аутентифицироваться, поэтому я что-то упускаю, наверное...
1 ответ
Если ваша функция getClientCredential() вызывается, тогда ваш клиент пытается аутентифицироваться. Если нет, то журналы сервера (такие как /var/log/nginx/access.log) могут указывать причину.
Класс PKCS12 в этом ответе работал для меня.
Относительно цепочки для ключей, эта документация Apple говорит
Чтобы использовать цифровые идентификаторы в своих собственных приложениях, вам нужно будет написать код для их импорта. Обычно это означает чтение в BLOB-формате, отформатированном в PKCS#12, а затем импортирование содержимого этого двоичного объекта в цепочку ключей приложения с использованием функции SecPKCS12Import, описанной в справочнике по сертификатам, ключам и службам доверия.
Таким образом, новые элементы цепочки для ключей создаются с помощью группы доступа цепочки для ключей вашего приложения.