Ошибка HTTP-загрузки NSURLSession/NSURLConnection (kCFStreamErrorDomainSSL, -9802 в iOS10, 2017 г.

ЭТО НЕ ПОВТОРЯЮЩИЙСЯ ВОПРОС, ЧИТАЙТЕ ДАЛЬШЕ. Я обновляю устаревший код в своем приложении до соответствия iOS10.

ОШИБКА: NSURLSession доставляет мне неприятности с этой печально известной ошибкой, наряду с 9806 и 9801, в зависимости от того, что я поместил в файл .plist:

NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9802)

МОЙ КОД: В моем файле Info.plist у меня есть:

<key>NSAppTransportSecurity</key>
    <dict>
        <key>NSExceptionDomains</key>
        <dict>
            <key>https://my.own-server.com</key>
            <dict>
                <key>NSIncludesSubdomains</key>
                <true/>
                <key>NSExceptionAllowInsecureHTTPSLoads</key>
                <true/>
                <key>NSExceptionRequiresForwardSecrecy</key>
                <true/>
                <key>NSExceptionMinimumTLSVersion</key>
                <string>TLSv1.0</string>
                <key>NSThirdPartyExceptionAllowInsecureHTTPSLoads</key>
                <false/>
                <key>NSThirdPartyExceptionRequiresForwardSecrecy</key>
                <false/>
                <key>NSThirdPartyExceptionMinimumTLSVersion</key>
                <string>TLSv1.0</string>
                <key>NSRequiresCertificateTransparency</key>
                <false/>
            </dict>
        </dict>
        <key>NSAppTransportSecurity</key>
        <dict>
            <key>NSAllowsArbitraryLoads</key>
            <false/>
        </dict>
    </dict>

В моем коде ObjectiveC у меня есть это:

NSURLSessionConfiguration *defaultConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session __unused = [NSURLSession sessionWithConfiguration:defaultConfiguration delegate:[PortalRequest alloc] delegateQueue:[NSOperationQueue mainQueue]]; operationQueue:[NSOperationQueue mainQueue]];
    requestContainer.sessionDataTask = [session dataTaskWithRequest:request];
[self.sessionDataTask resume];

В методе DELEGATE класса, в котором выполняется вызов URLSession, я вижу didReceiveChallenge:

LOG: ** NSURLSession IOS10 ** - didReceiveChallenge called

... и, наконец, я получаю сообщение об ошибке:

[TIMESTAMP] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9806)

СЕРВЕР Ни одно из решений, предложенных в других ответах, не работает, это из-за крайнего срока Apple в 2017 году? https://techcrunch.com/2016/06/14/apple-will-require-https-connections-for-ios-apps-by-the-end-of-2016/ В соответствии с этим интерактивный инструмент анализа безопасности, https://www.ssllabs.com/ssltest/analyze.html Серверная часть ПОДДЕРЖИВАЕТ TLS 1.2

Любые идеи о том, как решить эту проблему?? Вы знаете, где найти пример кода iOS, чтобы быть на 100% уверенным, что конечная точка, на которую я указываю, свободна от вины?

ОБНОВЛЕНИЕ Этот код работает для меня, есть мнения по этому поводу?:

- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler{
    NSLog(@"*** KBRequest.NSURLSessionDelegate - didReceiveChallenge IOS10");

    if([challenge.protectionSpace.authenticationMethod
        isEqualToString:NSURLAuthenticationMethodServerTrust])
    {
        if([challenge.protectionSpace.host
            isEqualToString:@"engine.hello-indigo.com"])
        {
            NSURLCredential *credential =
            [NSURLCredential credentialForTrust:
             challenge.protectionSpace.serverTrust];
            completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
        }
        else
            completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
    }
}

person Josh    schedule 25.01.2017    source источник
comment
Если вы реализуете свою собственную обработку вызовов аутентификации, мое первое предположение будет ошибкой. Можете ли вы опубликовать содержимое этого метода?   -  person dgatwood    schedule 30.01.2017
comment
Как вы решаете свою проблему? потому что у меня есть аудиопоток http и NSURLConnection завершен с ошибкой - код -1002   -  person Genevios    schedule 12.10.2017
comment
@Genevios Я решил это, посмотри на обновление по моему вопросу. Это работает, и никто не предлагает лучшую идею, так что я буду считать ее хорошей. Что касается ошибки кода 1002, убедитесь, что вы используете https в своем URL-адресе stackoverflow.com/questions/26647423/   -  person Josh    schedule 15.10.2017
comment
@Josh О, мой друг, я использую аудиопоток, и как я понимаю, у аудиопотока нет https ... это правда или нет? как ты думаешь?   -  person Genevios    schedule 16.10.2017
comment
@Genevios звучит сложно, но вы отправляете этот аудиопоток на какой-то адрес сервера, верно? Этот адрес должен начинаться с http или https.   -  person Josh    schedule 16.10.2017
comment
Да, мой адрес начинается с http, но я снова получаю ту же ошибку.. =\   -  person Genevios    schedule 17.10.2017
comment
Вы пробовали использовать свой адрес как Http, так и Https? @Женевиос   -  person Josh    schedule 17.10.2017
comment
Да, но если я использую https, мой запрос не работает, потому что у меня нет https на сервере.   -  person Genevios    schedule 17.10.2017
comment
@Genevios, в этом случае добавление этого сервера в качестве исключения в файл .plist может сработать.   -  person Josh    schedule 17.10.2017
comment
Как я могу это сделать? Я уже видел много примеров, но ни одного идеального. Я имею в виду, какую строку я должен добавить?   -  person Genevios    schedule 18.10.2017
comment
Щелкните правой кнопкой мыши файл info.plist › откройте › как источник. (мой .plist находился в папке «Вспомогательные файлы») и попробуйте добавить исключение для URL-адреса, следуя этим двум примерам: txt.do/ d4fix   -  person Josh    schedule 18.10.2017
comment
Круто попробую вставить   -  person Genevios    schedule 04.11.2017


Ответы (3)


Попробуйте установить для NSExceptionRequiresForwardSecrecy значение false. Однако ваш сервер соответствует минимальным требованиям для версии протокола Transport Layer Security (TLS), ваш сервер Connection-Cipher может не поддерживать прямую секретность.

Согласованная версия безопасности транспортного уровня (TLS) должна быть TLS 1.2. Попытки подключения без защиты TLS/SSL или с использованием более старой версии TLS/SSL по умолчанию отклоняются.

Соединение должно использовать симметричный шифр AES-128 или AES-256.

Согласованный набор шифров соединения TLS должен поддерживать совершенную прямую секретность (PFS) посредством обмена ключами на основе эллиптических кривых Диффи-Хеллмана (ECDHE) и должен быть одним из следующих:

TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA

person Idali    schedule 25.01.2017
comment
Инструмент безопасности показывает, что все эти наборы шифров поддерживаются. Ошибка возникает сразу после вызова метода делегата didReceiveChallenge (как показано в моем последнем редактировании выше) - person Josh; 25.01.2017

Наконец-то я решил проблему! Я вынул исключения из файла .plist и добавил этот код в метод didReceiveChallenge Delegate:

- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler{
    NSLog(@"*** KBRequest.NSURLSessionDelegate - didReceiveChallenge IOS10");
    completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:[[challenge protectionSpace] serverTrust]]);
}

Эта статья оказалась очень полезной: http://timekl.com/blog/2015/08/21/shipping-an-app-with-app-transport-security/

person Josh    schedule 03.02.2017
comment
@dgatwood вы правы, но никто нигде не объясняет, как это сделать правильно. Если у меня еще будет время, я постараюсь исправить это. На данный момент мне нужно восстановить скорость после всего времени, которое я потратил впустую на эту ловушку, и получить свое приложение в магазине. - person Josh; 04.02.2017
comment
Вы абсолютно не можете отправить это в магазин приложений с помощью этого хака. Ваше приложение почти наверняка будет отклонено. Я опубликовал пример обработчика завершения, который загружает пользовательский корневой сертификат из вашего пакета приложений и проверяет запросы по нему. - person dgatwood; 05.02.2017
comment
Не могли бы вы поделиться своим примером в качестве ответа? Обещаю зеленую галочку, если получится. @dgatwood - person Josh; 06.02.2017

Чтобы избежать полного взлома TLS (не говоря уже о других формах аутентификации), вы должны сделать что-то вроде этого:

- (void)URLSession:(NSURLSession *)session
    didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
      completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
                                  NSURLCredential *credential))completionHandler
    {
        NSURLProtectionSpace *protectionSpace = [challenge protectionSpace];
        if ([protectionSpace authenticationMethod] == NSURLAuthenticationMethodServerTrust) {
            // Load our trusted root certificate from the app bundle.
            SecTrustRef trust = [protectionSpace serverTrust];
            NSURL *certificatePath = [[NSBundle mainBundle] URLForResource:@"customRootCert" ofType:@"der"];
            NSData *certificateData = [NSData dataWithContentsOfURL:resourcePath];
            SecCertificateRef trustedCert = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData);

            // Change the trust object to trust our root cert.
            trust = addAnchorToTrust(trust, trustedCert);

            SecTrustResultType secresult = kSecTrustResultInvalid;
            if (SecTrustEvaluate(trust, &secresult) != errSecSuccess) {
                // Something went horribly wrong.
                completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
                return;
            }

            switch (secresult) {
                case kSecTrustResultUnspecified: // The OS trusts this certificate implicitly.
                case kSecTrustResultProceed: // The user explicitly told the OS to trust it.
                {
                    NSURLCredential *credential =
                        [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                    completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, credential)
                    return;
                }
                default:
                    /* It's somebody else's key/cert. Fall through. */
            }
            completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
        } else {
            // If we aren't checking the server's public key, just use
            // the default behavior.
            completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
        }
    }

Обратите внимание, что этот код не компилируется и не тестировался, поэтому не стесняйтесь исправлять опечатки и другие ошибки.

person dgatwood    schedule 04.02.2017
comment
Я написал фрагмент, который также работает, основываясь на вашем предложении (и добавил его к вопросу выше). Как вы думаете, это безопасно? Спасибо. - person Josh; 06.02.2017
comment
Нет, это не безопасно. Размещенный вами код означает, что любой может предоставить любой сертификат с любым ключом для этого имени хоста. Это эффективно снижает безопасность запросов вашего приложения до незашифрованного HTTP, и если вы собираетесь это сделать, вы можете просто использовать HTTP и запросить исключение (удачи). - person dgatwood; 06.02.2017