2015-09-15 3 views
9

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

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

затем метод «подключение: didFailWithError:» вызывается со следующей ошибкой:

Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x150094100>, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, NSErrorPeerCertificateChainKey=<CFArray 0x1500ddd90 [0x19f6dab68]>{type = immutable, count = 1, values = (
    0 : <cert(0x14e6fb370) s: (server certificate name) i: (custom CA name)> 
)}, NSUnderlyingError=0x1504ae170 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSErrorFailingURLStringKey=https://217.92.80.156:9090/(method name and parameters), NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFNetworkCFStreamSSLErrorOriginalValue=-9802, kCFStreamPropertySSLPeerCertificates=<CFArray 0x1500ddd90 [0x19f6dab68]>{type = immutable, count = 1, values = (
    0 : <cert(0x14e6fb370) s: (server certificate name) i: (custom CA name)> 
)}, _kCFStreamPropertySSLClientCertificateState=2, kCFStreamPropertySSLPeerTrust=<SecTrustRef: 0x150094100>, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., _kCFStreamPropertySSLClientCertificates=<CFArray 0x14e5ee8e0 [0x19f6dab68]>{type = mutable-small, count = 2, values = (
    0 : <SecIdentityRef: 0x15012cd40> 
    1 : <cert(0x15014aa70) s: (client certificate name) i: (custom CA name)> 
)}, _kCFStreamErrorDomainKey=3, NSErrorFailingURLKey=https://217.92.80.156:9090/(method name and parameters), _kCFStreamErrorCodeKey=-9802}}, NSErrorClientCertificateChainKey=<CFArray 0x14e5ee8e0 [0x19f6dab68]>{type = mutable-small, count = 2, values = (
    0 : <SecIdentityRef: 0x15012cd40> 
    1 : <cert(0x15014aa70) s: (client certificate name) i: (custom CA name)> 
)}, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://217.92.80.156:9090/(method name and parameters), NSErrorFailingURLStringKey=https://217.92.80.156:9090/(method name and parameters), NSErrorClientCertificateStateKey=2} 

App получает две проблемы аутентификации (NSURLAuthenticationMethodClientCertificate и NSURLAuthenticationMethodServerTrust) и обрабатывает их в следующем порядке:

- (void) connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge 
{ 
    if(challenge.proposedCredential && !challenge.error) 
    { 
     [challenge.sender useCredential:challenge.proposedCredential forAuthenticationChallenge:challenge]; 

     return; 
    } 

    NSString *strAuthenticationMethod = challenge.protectionSpace.authenticationMethod; 
    NSLog(@"authentication method: %@", strAuthenticationMethod); 

    NSURLCredential *credential = nil; 
    if([strAuthenticationMethod isEqualToString:NSURLAuthenticationMethodClientCertificate]) 
    { 
     // get identity and certificate from p.12 
     NSData *PKCS12Data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"client" ofType:@"p12"]]; 

     NSDictionary *optionsDictionary = [NSDictionary dictionaryWithObject:@"password" forKey:(__bridge id)kSecImportExportPassphrase]; 
     CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL); 
     OSStatus securityError = SecPKCS12Import((__bridge CFDataRef)PKCS12Data,(__bridge CFDictionaryRef)optionsDictionary, &items); 

     SecIdentityRef identity = NULL; 
     SecCertificateRef certificate = NULL; 
     if(securityError == errSecSuccess) 
     { 
      CFDictionaryRef myIdentityAndTrust = CFArrayGetValueAtIndex(items, 0); 
      identity = (SecIdentityRef)CFDictionaryGetValue (myIdentityAndTrust, kSecImportItemIdentity); 

      CFArrayRef array = (CFArrayRef)CFDictionaryGetValue(myIdentityAndTrust, kSecImportItemCertChain); 
      certificate = (SecCertificateRef)CFArrayGetValueAtIndex(array, 0); 
     } 

     credential = [NSURLCredential credentialWithIdentity:identity certificates:[NSArray arrayWithObject:(__bridge id)(certificate)] persistence:NSURLCredentialPersistenceNone]; 

     CFRelease(items); 
    } 
    else if([strAuthenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) 
    {  
     int trustCertificateCount = (int)SecTrustGetCertificateCount(challenge.protectionSpace.serverTrust); 
     NSMutableArray *trustCertificates = [[NSMutableArray alloc] initWithCapacity:trustCertificateCount]; 
     for(int i = 0; i < trustCertificateCount; i ++) 
     { 
      SecCertificateRef trustCertificate = SecTrustGetCertificateAtIndex(challenge.protectionSpace.serverTrust, i); 
      [trustCertificates addObject:(__bridge id) trustCertificate]; 
     }    

     SecPolicyRef policyRef = NULL; 
     policyRef = SecPolicyCreateSSL(YES, (__bridge CFStringRef) challenge.protectionSpace.host); 

     SecTrustRef trustRef = NULL; 
     if(policyRef) 
     { 
      SecTrustCreateWithCertificates((__bridge CFArrayRef) trustCertificates, policyRef, &trustRef); 
      CFRelease(policyRef); 
     } 

     if(trustRef) 
     { 
//   SecTrustSetAnchorCertificates(trustRef, (__bridge CFArrayRef) [NSArray array]); 
//   SecTrustSetAnchorCertificatesOnly(trustRef, NO); 

      SecTrustResultType result; 
      OSStatus trustEvalStatus = SecTrustEvaluate(trustRef, &result); 
      if(trustEvalStatus == errSecSuccess) 
      { 
       // just temporary attempt to make it working. 
       // i hope, there is no such problem, when we have final working version of certificates. 
       if(result == kSecTrustResultRecoverableTrustFailure) 
       { 
        CFDataRef errDataRef = SecTrustCopyExceptions(trustRef); 
        SecTrustSetExceptions(trustRef, errDataRef); 

        SecTrustEvaluate(trustRef, &result); 
       } 

       if(result == kSecTrustResultProceed || result == kSecTrustResultUnspecified) 
        credential = [NSURLCredential credentialForTrust:trustRef]; 
      } 

      CFRelease(trustRef); 
     } 
    } 
    else 
    { 
     DDLogWarn(@"Unexpected authentication method. Cancelling authentication ..."); 
     [challenge.sender cancelAuthenticationChallenge:challenge]; 
    } 

    if(credential) 
     [challenge.sender useCredential:credential forAuthenticationChallenge:challenge]; 
    else 
     [challenge.sender cancelAuthenticationChallenge:challenge]; 
} 

В журнале диагностики CFNetwork я вижу, что процедура Handshake готова к запуску. По крайней мере, приложение отправляет сообщение «ClientHello», затем сервер отправляет сообщение «ServerHello» и требует аутентификации. И здесь приложение пытается отправить ответ аутентификации, но сразу получает ошибку. (В то же время в журналах сервера я вообще не вижу сообщений о рукопожатии). Вот часть диагностического журнала:

Sep 15 10:51:49 AppName[331] <Notice>: CFNetwork Diagnostics [3:49] 10:51:49.185 { 
    Authentication Challenge 
     Loader: <CFURLRequest 0x1501931c0 [0x19f6dab68]> {url = https://217.92.80.156:9090/(method name and parameters), cs = 0x0} 
    Challenge: challenge space https://217.92.80.156:9090/, ServerTrustEvaluationRequested (Hash f9810ad8165b3620) 
    } [3:49] 
Sep 15 10:51:49 AppName[331] <Notice>: CFNetwork Diagnostics [3:50] 10:51:49.189 { 
    Use Credential 
     Loader: <CFURLRequest 0x1501931c0 [0x19f6dab68]> {url = https://217.92.80.156:9090/(method name and parameters), cs = 0x0} 
    Credential: Name: server, Persistence: session 
    } [3:50] 
Sep 15 10:51:49 AppName[331] <Notice>: CFNetwork Diagnostics [3:51] 10:51:49.190 { 
    touchConnection 
       Loader: <CFURLRequest 0x1501931c0 [0x19f6dab68]> {url = https://217.92.80.156:9090/(method name and parameters), cs = 0x0} 
    Timeout Interval: 60.000 seconds 
    } [3:51] 
Sep 15 10:51:49 AppName[331] <Notice>: CFNetwork Diagnostics [3:52] 10:51:49.192 { 
    Response Error 
    Request: <CFURLRequest 0x14e5d02a0 [0x19f6dab68]> {url = https://217.92.80.156:9090/(method name and parameters), cs = 0x0} 
     Error: Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFNetworkCFStreamSSLErrorOriginalValue=-9802, kCFStreamPropertySSLPeerCertificates=<CFArray 0x1500ddd90 [0x19f6dab68]>{type = immutable, count = 1, values = (
       0 : <cert(0x14e6fb370) s: (server certificate name) i: (custom CA name)> 
      )}, _kCFStreamPropertySSLClientCertificateState=2, kCFStreamPropertySSLPeerTrust=<SecTrustRef: 0x150094100>, _kCFStreamPropertySSLClientCertificates=<CFArray 0x14e5ee8e0 [0x19f6dab68]>{type = mutable-small, count = 2, values = (
       0 : <SecIdentityRef: 0x15012cd40> 
       1 : <cert(0x15014aa70) s: (client certificate name) i: (custom CA name)> 
      )}, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802} 
    } [3:52] 

Наш фоновым экземпляр может быть установлен на стороне клиента, поэтому я не могу установить любое исключение домена в файле Info.plist. Также приложение может запрашивать сервер по IP-адресу в форме IPv4, но не по имени домена (как это в моем примере).

Что есть я пробовал:

  • используется NSURLSession вместо NSURLConnection, но без какого-либо успеха;
  • проверял требования ATS от Apple для реализации сервера here (разработчик-разработчик уверен, что его реализация соответствует всем им);
  • играл с установкой якорных сертификатов для проверки достоверности в соответствии с различными разрешенными проблемами из stackoverflow и форумов разработчиков Apple;
  • уделял особое внимание сообщению similar и связанным с ним solution на форумах разработчиков;

Я тестирую запрос https на iPad Air 2 с семенами iOS 9 GM (Build 13A340) и xCode 7 GM Seed (Build 7A218). Важное замечание: эта функциональность отлично работает с iOS 8. Учитывая это, я могу предположить, что проблема на нашем сервере, но наш сторонний разработчик заверил меня, что все в порядке.

Теперь у меня нет идей. Я был бы признателен, если бы кто-нибудь мог дать мне подсказку или хотя бы предложить какую-то другую диагностику, которая бы выявила конкретную ошибку, более конкретную, чем «фатальное предупреждение».

Спасибо.

EDIT 1: SecTrustEvaluate всегда возвращает kSecTrustResultRecoverableTrustFailure, поэтому мне пришлось найти какое-то обходное решение.

+0

Вы нашли решение? У меня тоже есть эта проблема, и я бы хотел использовать свой локальный сервер для тестирования, но с самоподписанным сертификатом работать невозможно, и я получаю те же ошибки ... –

+0

Пока нет, мне нужно работать над другими высокоприоритетными задачами по нескольким причинам. Я свяжусь с вами, если у меня есть решение. – alfared

+0

Это ошибка протокола IOS PITA. (PITA означает боль в ....) – Josh

ответ

0

Эта проблема была решена некоторое время назад. Это оказалось недействительным самозаверяющим сертификатом. Он не отвечал всем требованиям Apple. К сожалению, я не знаю, что именно.

3

Вы использовали nscurl для диагностики проблемы подключения? Если у вас есть Mac OS X работает v10.11 вы можете запустить что-то вроде этого:

/usr/bin/nscurl --ats-diagnostics https://www.yourdomain.com 

В качестве альтернативы, если у вас нет 10.11, вы можете скачать образец кода здесь: https://developer.apple.com/library/mac/samplecode/SC1236/ и построить его с XCode и запустить его, как это (изменить путь, необходимые для вашей машины):

/Users/somebody/Library/Developer/Xcode/DerivedData/TLSTool-hjuytnjaqebcfradighsrffxxyzq/Build/Products/Debug/TLSTool s_client -connect www.yourdomain.com:443 

(Чтобы найти полный путь к вышесказанному, после вы создали, откройте группу «Продукты» в своем Навигаторе проектов, щелкните правой кнопкой мыши на TLSTool и «Показать в Finder».)

Вы уже связаны с техникой Apple на эту тему, https://developer.apple.com/library/prerelease/ios/technotes/App-Transport-Security-Technote/, но вы не сказали, если вы бежал nscurl или нет.

+0

Конечная точка моей виртуальной машины проходит все тесты в/usr/bin/nscurl --ats-diagnostics [https: //mydomain.local: 8888] (https: // mydomain. local: 8888), но я все еще получаю ошибку в симуляторе. –

3

В соответствии с этим: https://forums.developer.apple.com/message/36842#36842

Лучше исправить HTTP не удалось загрузить (kCFStreamErrorDomainSSL, -9802), чтобы установить исключение в info.plist файле следующим образом:

<key>NSAppTransportSecurity</key> 
<dict> 
    <key>NSExceptionDomains</key> 
    <dict> 
    <key>test.testdomain.com</key> 
    <dict> 
     <key>NSIncludesSubdomains</key> 
     <true/> 
     <key>NSExceptionAllowsInsecureHTTPLoads</key> 
     <true/> 
    </dict> 
    </dict> 
</dict> 

Важно Дело в том, что это не менее безопасно, чем iOS8, но не настолько безопасно, как полный ATS, поддерживаемый iOS9.

+1

Спасибо, отлично работает после закрытия тегов dict. – dlw

+1

Большое спасибо, но: 1. Это позволяет обычную загрузку HTTP, и мне нужен HTTPS (по соображениям безопасности). 2. Как я уже писал, я не могу установить какой-либо домен в списке исключений, потому что наши клиенты часто устанавливают сервер на своих серверах. Например, это могут быть «server.company1.com» и «server.company2.com». В вашем ответе предполагается, что мне придется перестроить приложение и добавить новый домен исключения «server.company3.com» после того, как мы продаем приложение третьей компании. – alfared

+0

Возможно, вы сможете использовать комбинацию информации из документации Apple по ATS , а также эту запись в блоге на самоподписанных сертификатах # 5, в частности , чтобы решить вашу проблему (вам, вероятно, потребуется повторить несколько раз, пока вы не поймете это правильно). – spirographer

0

Я только что встретил ту же проблему с ur's.Now я фиксирую it.It потому, что версия ДУСА и сертификат sign.As документ яблока сказать ниже apple's document

Так что я сделать эту вещь info.plist setting

и он работает

 Смежные вопросы

  • Нет связанных вопросов^_^