Как я могу принять самозаверяющий сертификат SSL, используя NSURLSession iOS 7 и его семейство методов делегата для целей разработки?

Я занимаюсь разработкой приложения для iPhone. Во время разработки мне нужно подключиться к серверу, который использует самозаверяющий сертификат SSL. Я уверен - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler это моя возможность написать код исключения, чтобы разрешить это. Тем не менее, я не могу найти никаких ресурсов, которые говорят мне, как это сделать. Я вижу следующую ошибку в журнале:

NSURLConnection/CFURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813)

В дополнение к этому, когда я NSLog(@"error = %@", error); из вышеупомянутого метода делегата я получаю:

Ошибка Domain=NSURLErrorDomain Code=-1202 "Сертификат для этого сервера недействителен. Возможно, вы подключаетесь к серверу, который притворяется" api.mydevelopmenturl.com ", что может поставить под угрозу вашу конфиденциальную информацию." UserInfo=0x10cbdbcf0 {NSUnderlyingError=0x112ec9730 "Сертификат для этого сервера недействителен. Возможно, вы подключаетесь к серверу, который притворяется" api.mydevelopmenturl.com ", который может поставить под угрозу вашу конфиденциальную информацию.", NSErrorFailingURLStringKey = https://api.mydevelopmenturl.com/posts, NSErrorFailingURLKey = https://api.mydevelopmenturl.com/posts, NSLocalizedRecoverySuggestion = Вы все равно хотите подключиться к серверу?, NSURLErrorFailingURLPeerTrustErrorKey=, сертификат NSLocalized для этого = недействительный сертификат. Возможно, вы подключаетесь к серверу, который притворяется "api.mydevelopmenturl.com", что может поставить под угрозу вашу конфиденциальную информацию.}

Любые идеи о том, как решить эту проблему? Пожалуйста, отправьте код, поскольку я прочитал концептуальные документы, и я не понимаю их. Вот пример того, что мне не подходит: https://developer.apple.com/library/content/technotes/tn2232/_index.html

11 ответов

Решение

Это работает для меня:

NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:Nil];
...
...
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler{
  if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]){
    if([challenge.protectionSpace.host isEqualToString:@"mydomain.com"]){
      NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
      completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
    }
  }
}

У Apple есть Техническая нота 2232, которая довольно информативна и подробно объясняет оценку доверия сервера HTTPS.

В этом случае ошибка -1202 в NSURLErrorDomain домен NSURLErrorServerCertificateUntrusted, что означает, что оценка доверия сервера не удалась. Вы также можете получить множество других ошибок; Приложение A: Общие ошибки оценки доверия сервера перечисляет наиболее распространенные ошибки.

Из технической записки:

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

Особый интерес к этому вопросу имеет раздел об оценке доверия к серверу NSURLSession:

NSURLSession позволяет настроить оценку доверия HTTPS-сервера путем реализации -URLSession:didReceiveChallenge:completionHandler: метод делегата. Чтобы настроить оценку доверия HTTPS-сервера, найдите запрос, в области защиты которого используется метод проверки подлинности: NSURLAuthenticationMethodServerTrust, Для этих проблем, решите их, как описано ниже. Для других задач, которые вас не волнуют, вызовите блок обработчика завершения с NSURLSessionAuthChallengePerformDefaultHandling расположение и NULL учетные данные.

Имея дело с задачей аутентификации NSURLAuthenticationMethodServerTrust, вы можете получить объект доверия из пространства защиты запроса, вызвав метод -serverTrust. После использования объекта доверия для выполнения собственной пользовательской оценки доверия сервера HTTPS вы должны решить проблему одним из двух способов:

Если вы хотите запретить соединение, вызовите блок обработчика завершения с NSURLSessionAuthChallengeCancelAuthenticationChallenge расположение и NULL учетные данные.

Если вы хотите разрешить соединение, создайте учетные данные из вашего объекта доверия (используя +[NSURLCredential credentialForTrust:]) и вызвать блок обработчика завершения с этими учетными данными и NSURLSessionAuthChallengeUseCredential распоряжения.

Результатом всего этого является то, что если вы реализуете следующий метод делегата, вы можете переопределить доверие сервера для конкретного сервера:

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
{
    if([challenge.protectionSpace.authenticationMethod
                           isEqualToString:NSURLAuthenticationMethodServerTrust])
    {
        if([challenge.protectionSpace.host
                           isEqualToString:@"domaintoverride.com"])
        {
            NSURLCredential *credential = 
                          [NSURLCredential credentialForTrust:
                                          challenge.protectionSpace.serverTrust];
            completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
        }
        else
            completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
    }
}

Обратите внимание, что вы должны обрабатывать как случай хоста, совпадающий с тем, который вы хотите переопределить, так и все другие случаи. Если вы не обрабатываете часть "все другие случаи", результат поведения не определен.

Найдите надежный центр сертификации SSL в Интернете, который предлагает бесплатную 90-дневную пробную версию для новых сертификатов. Установите сертификат на свой сервер. Теперь у вас есть 90 дней, чтобы разработать приложение, чтобы вы могли принять решение о том, стоит ли платить деньги за "продление" сертификата. Это лучший ответ для меня, так как мое решение использовать самозаверяющий сертификат было финансово мотивировано, и 90 дней дают мне достаточно времени для разработки приложения до такой степени, что я могу решить, стоит ли тратить деньги на сертификат SSL или нет, Этот подход избавляет от необходимости иметь дело с последствиями безопасности для запуска кодовой базы, которая настроена для принятия самозаверяющих сертификатов. Милая! Yay для начальной загрузки!

Сделай себе огромную услугу и не делай этого.

Начните с чтения статьи Самый опасный код в мире: проверка SSL-сертификатов в не браузерном программном обеспечении, особенно в разделе 10 "Взлом или отключение проверки сертификатов". Он специально вызывает блог, посвященный какао, в котором подробно описывается, как делать то, что вы просите.

Но не надо. Отключение проверки SSL-сертификата похоже на введение бомбы замедленного действия в ваше приложение. Когда-нибудь, когда-нибудь, он будет случайно оставлен включенным, и сборка попадет в дикую природу. И в этот день ваши пользователи будут подвергнуты серьезному риску.

Вместо этого вам следует использовать сертификат, подписанный промежуточным сертификатом, который вы можете установить и доверять этому конкретному устройству, что позволит успешно пройти проверку SSL, не подвергая опасности любое другое устройство, кроме вашего (и только тогда, временно).

Для Swift 3.0 / 4

Если вы просто хотите разрешить любой вид самозаверяющих сертификатов, вы можете использовать следующий подход для реализации URLSessionDelegate. Apple предоставляет дополнительную информацию о том, как использовать URLSessionDelegate для всех видов методов аутентификации: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/URLLoadingSystem/Articles/AuthenticationChallenges.html

Сначала реализуйте метод делегата и назначьте соответствующего делегата:

let urlSession = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
let task = urlSession.dataTask(with: urlRequest).resume()

Теперь реализуйте метод делегата https://developer.apple.com/documentation/foundation/nsurlsessiondelegate/1409308-urlsession?language=objc

func urlSession(_ session: URLSession, 
     didReceive challenge: URLAuthenticationChallenge, 
        completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {

    guard challenge.previousFailureCount == 0 else {
        challenge.sender?.cancel(challenge)
        // Inform the user that the user name and password are incorrect
        completionHandler(.cancelAuthenticationChallenge, nil)
        return
    }

    // Within your authentication handler delegate method, you should check to see if the challenge protection space has an authentication type of NSURLAuthenticationMethodServerTrust
    if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust
       // and if so, obtain the serverTrust information from that protection space.
       && challenge.protectionSpace.serverTrust != nil
       && challenge.protectionSpace.host == "yourdomain.com" {
        let proposedCredential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
        completionHandler(URLSession.AuthChallengeDisposition.useCredential, proposedCredential)
    }
}

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

func urlSession(_ session: URLSession, 
     didReceive challenge: URLAuthenticationChallenge, 
        completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {

    guard challenge.previousFailureCount == 0 else {
        challenge.sender?.cancel(challenge)
        // Inform the user that the user name and password are incorrect
        completionHandler(.cancelAuthenticationChallenge, nil)
        return
    }

    if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust
       && challenge.protectionSpace.serverTrust != nil
       && challenge.protectionSpace.host == "yourdomain.com" {

        if let trust = challenge.protectionSpace.serverTrust,
           let pem = Bundle.main.url(forResource:"cert", withExtension: "cer"),
           let data = NSData(contentsOf: pem),
           let cert = SecCertificateCreateWithData(nil, data) {
            let certs = [cert]
            SecTrustSetAnchorCertificates(trust, certs as CFArray)
            var result=SecTrustResultType.invalid
            if SecTrustEvaluate(trust,&result)==errSecSuccess {
              if result==SecTrustResultType.proceed || result==SecTrustResultType.unspecified {
                let proposedCredential = URLCredential(trust: trust)
                completionHandler(.useCredential,proposedCredential)
                return
              }
            }

        }
    }
    completionHandler(.performDefaultHandling, nil)
}

То же, что и решение Фрихерда, но быстро:

func URLSession(session: NSURLSession, task: NSURLSessionTask, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
    if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust{
        let credential = NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!)
        completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential,credential);
    }
}

Просто нужно добавить.cer в SecTrust и передать на ATS

class NSURLSessionPinningDelegate: NSObject, URLSessionDelegate {

    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void) {

        if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) {
            if let trust = challenge.protectionSpace.serverTrust,
               let pem = Bundle.main.path(forResource: "https", ofType: "cer"),
               let data = NSData(contentsOfFile: pem),
               let cert = SecCertificateCreateWithData(nil, data) {
                let certs = [cert]
                SecTrustSetAnchorCertificates(trust, certs as CFArray)

                completionHandler(URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust: trust))
                return
            }
        }

        // Pinning failed
        completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
    }
}

Обновить Xcode 9

    var result:(message:String, data:Data?) = (message: "Fail", data: nil)
    var request = URLRequest(url: url)

    let sessionDelegate = SessionDelegate()
    let session = URLSession(configuration: .default, delegate: sessionDelegate, delegateQueue: nil)
    let task = session.dataTask(with: request){(data, response, error) in


    }
    task.resume()

задача делегата

    class SessionDelegate:NSObject, URLSessionDelegate
    {

        func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
            if(challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust)
            {
                print(challenge.protectionSpace.host) 
                if(challenge.protectionSpace.host == "111.11.11.11")
                {
                    let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
                   completionHandler(URLSession.AuthChallengeDisposition.useCredential, credential)
                }
            }

        }
    }

Вот решение, которое сработало для меня. Вам необходимо принять соединение через делегата соединения, включая оба сообщения:

- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
    return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
        [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];

    [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}

Обратите внимание, что при этом вы не проверяете надежность сертификата, поэтому интересным является только шифрование SSL-соединения HTTPS, но здесь не учитываются права подписи, что может снизить безопасность.

Возможно, лучшим способом является предоставление пользователю возможности принять сертификат, подтверждающий (визуально), что URL является точным для доступа к услуге. Например, если в какой-либо настройке приложения введен хост, протестируйте его при входе пользователя, и пусть пользователь сам решит.

Учтите, что эта тактика "подтверждения пользователя" используется Safari и, таким образом, одобряется Apple, было бы логично использовать ее для других приложений.

Предложите покопаться в NSErrorRecoveryAttempting (не делаю сам) http://apple.co/22Au1GR

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

Это похоже на то, что Apple реализовала бы по природе в Какао, но пока я не нашел "легкой кнопки". Хотелось бы, чтобы флаг "kLetUserDecide" на чем-то в NSURL или NSURLSession вместо того, чтобы всем приходилось реализовывать метод делегата, а также протокол NSErrorRecoveryAttempting.

Это прекрасно работает для меня, чтобы пройти самоподписанный:

Delegate : NSURLSessionDelegate

- (void)URLSession:(NSURLSession *)session **task**:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
}
Другие вопросы по тегам