it-swarm.com.ru

Как я могу принять самозаверяющий сертификат 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 =, NSLocalizedDescription = Сертификат для этого сервера недействителен . Возможно, вы подключаетесь к серверу, который притворяется «Api.mydevelopmenturl.com», который может сделать вашу конфиденциальную информация под угрозой.}

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

54
John Erck

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

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);
    }
  }
}
105
friherd

У 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);
    }
}

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

24
memmons

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

11
John Erck

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

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

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

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

9
Shaggy Frog

То же, что и решение Фрихерда, но в Swift:

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);
    }
}
6
eric

Для 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)
}
3
Lepidopteron

просто нужно добавить .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)
    }
}
1
yycking

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

- (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-соединения, но здесь не учитывается право подписи, что может снизить безопасность.

0
Maxime T

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

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]);
}
0
Riyaz shaik riyaz

Обновление 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)
                }
            }

        }
    }
0
luhuiya

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

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

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

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

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

0
William Cerniuk