NSURLCredentialStorage setDefaultCredential: не работает для [NSURLSession sharedSession]

Я в тупике: моему приложению необходимо подключиться к серверу, который использует самозаверяющие сертификаты для HTTPS и требует аутентификации на стороне клиента. Хуже того, мне действительно нужен медиаплеер iOS для подключения к этому серверу, поэтому я следовал инструкция для этого в письме:

credential = [NSURLCredential credentialWithIdentity:identity certificates:certs persistence:NSURLCredentialPersistenceForSession];
NSURLProtectionSpace *space = [[NSURLProtectionSpace alloc] initWithHost:@"server.com" 
                                                                    port:0
                                                                protocol:NSURLProtectionSpaceHTTPS 
                                                                   realm:nil 
                                                    authenticationMethod:NSURLAuthenticationMethodClientCertificate];
[[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credential forProtectionSpace:space];

Но это просто не сработает. Поэтому я попытался сделать запрос на сервер вручную:

NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@"https://server.com"]
                                       completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
                                           NSLog(@"Done : %@", error ? error : @"OK");
                                       }];

все, что я получаю, это эта ошибка:

2016-06-13 08:22:37.767 TestiOSSSL[3172:870700] CFNetwork SSLHandshake failed (-9824 -> -9829)
2016-06-13 08:22:37.793 TestiOSSSL[3172:870700] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9829)
2016-06-13 08:22:37.815 TestiOSSSL[3172:870685] Done : Error Domain=NSURLErrorDomain Code=-1206 "The server “ server.com” requires a client certificate." UserInfo={NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x13de519b0>, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9829, NSUnderlyingError=0x13de4f280 {Error Domain=kCFErrorDomainCFNetwork Code=-1206 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=1, kCFStreamPropertySSLPeerTrust=<SecTrustRef: 0x13de519b0>, _kCFNetworkCFStreamSSLErrorOriginalValue=-9829, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9829, kCFStreamPropertySSLPeerCertificates=<CFArray 0x13dda0a70 [0x1a0dc2150]>{type = immutable, count = 2, values = (
    0 : <cert(0x13dda4970) s: Server.com i: Localhost CA>
    1 : <cert(0x13dda50d0) s: Localhost CA i: Localhost CA>
)}}}, NSErrorPeerCertificateChainKey=<CFArray 0x13dda0a70 [0x1a0dc2150]>{type = immutable, count = 2, values = (
    0 : <cert(0x13dda4970) s: Server.com i: Localhost CA>
    1 : <cert(0x13dda50d0) s: Localhost CA i: Localhost CA>
)}, NSLocalizedDescription=The server “server.com” requires a client certificate., NSErrorFailingURLKey=https://server.com/, NSErrorFailingURLStringKey=https://server.com/, NSErrorClientCertificateStateKey=1}

Теперь, если я настрою свой собственный NSURLSession и использую обратный вызов URLSession:didReceiveChallenge:completionHandler::

NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
theSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
NSURLSessionDataTask *task = [theSession dataTaskWithURL:[NSURL URLWithString:@"https://server.com"]
                                       completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
                                           NSLog(@"Done : %@", error ? error : @"OK");
                                       }];

а потом:

- (void)URLSession:(NSURLSession *)session 
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
                             NSURLCredential *credential))completionHandler
{
    NSLog(@"Asking for credential");
    NSURLCredential *conf = [session.configuration.URLCredentialStorage defaultCredentialForProtectionSpace:challenge.protectionSpace];
    completionHandler(NSURLSessionAuthChallengeUseCredential, conf);
}

Обратите внимание, как я использую [session.configuration.URLCredentialStorage defaultCredentialForProtectionSpace:challenge.protectionSpace], что, как я полагаю, делает реализация NSURLSession по умолчанию, когда получает запрос аутентификации.

Это работает для этого конкретного соединения! Это доказывает, что учетные данные в порядке и что они правильно зарегистрированы в качестве учетных данных по умолчанию в NSURLCredentialStorage по умолчанию.

Но любое решение, связанное с обратным вызовом didReceiveChallenge:, бесполезно, потому что я не могу контролировать, какой NSURLSession использует медиаплеер.

Я пробовал взломать CustomHTTPProtocol, и это не помогло. тоже не работает.

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


person Guy Moreillon    schedule 13.06.2016    source источник


Ответы (1)


Несмотря на то, что сеанс по умолчанию и NSURLConnection разделяют множество функций, по-видимому, это не так. Вы пытались вызвать этот метод на [NSURLSession sharedSession].configuration.URLCredentialStorage?

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

person dgatwood    schedule 25.07.2016
comment
Спасибо за предложение, да, я пытался вызвать его в sharedSession, без радости. В любом случае, использование общего хранилища учетных данных никогда не сработает в моем случае, потому что iOS MediaPlayer вообще не работает в моем процессе, он полностью изолирован в отдельный процесс и делит экран только с моим приложением. Единственное решение, которое я смог найти, которое действительно работало, заключалось в настройке прокси-сервера HTTP в моем приложении, которое выдавало себя за сервер для медиаплеера и перенаправляло полученные запросы на фактический сервер, выполняя обработку учетных данных в своем собственном соединении. Громоздкий :-( - person Guy Moreillon; 25.07.2016
comment
Да, это проблема и для тех, кто делает что-то с веб-представлениями. Если вы еще этого не сделали, сообщите об ошибке в Apple и попросите предоставить учетные данные для медиаплеера. Между прочим, учетные данные для воспроизведения видео были постоянной проблемой на протяжении многих лет. На одном из моих веб-сайтов все еще есть хаки, чтобы обойти тот факт, что встроенное воспроизведение видео в Safari несколько лет назад не могло пройти основные или переварить учетные данные для аутентификации. Я сообщил об ошибке, и в конце концов они ее исправили, но очевидно, что это не та модель использования, о которой они много думают. Сообщите об ошибке, чтобы напомнить им, что аутентификация имеет значение. - person dgatwood; 26.07.2016