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, описанной в справочнике по сертификатам, ключам и службам доверия.

Таким образом, новые элементы цепочки для ключей создаются с помощью группы доступа цепочки для ключей вашего приложения.

Другие вопросы по тегам