Как использовать NSURLConnection для соединения с SSL для ненадежного сертификата?

У меня есть следующий простой код для подключения к веб-странице SSL

NSMutableURLRequest *urlRequest=[NSMutableURLRequest requestWithURL:url];
[ NSURLConnection sendSynchronousRequest: urlRequest returningResponse: nil error: &error ];

За исключением того, что выдает ошибку, если сертификат самоподписанный Error Domain=NSURLErrorDomain Code=-1202 UserInfo=0xd29930 "untrusted server certificate". Есть ли способ настроить его на прием соединений в любом случае (как в браузере, который вы можете нажать "Принять") или способ обойти это?

13 ответов

Решение

Для этого есть поддерживаемый API! Добавьте что-то вроде этого в свой NSURLConnection делегат:

- (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])
    if ([trustedHosts containsObject:challenge.protectionSpace.host])
      [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];

  [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}

Обратите внимание, что connection:didReceiveAuthenticationChallenge: может отправить свое сообщение на challenge.sender (намного) позже, после представления диалогового окна пользователю, если это необходимо, и т. д.

Если вы не желаете (или не можете) использовать частные API, есть библиотека с открытым исходным кодом (лицензия BSD) ASIHTTPRequest, которая предоставляет оболочку для нижнего уровня. CFNetwork APIs, Недавно они представили возможность HTTPS connections использование самозаверяющих или ненадежных сертификатов с -setValidatesSecureCertificate: API. Если вы не хотите извлекать всю библиотеку, вы можете использовать источник в качестве справочного материала для реализации той же функциональности самостоятельно.

В идеале должно быть только два сценария, когда приложению iOS потребуется принять ненадежный сертификат.

Сценарий А. Вы подключены к тестовой среде, в которой используется самозаверяющий сертификат.

Сценарий Б: Вы прокси HTTPS трафик, используя MITM Proxy like Burp Suite, Fiddler, OWASP ZAP, etc. Прокси вернут сертификат, подписанный самозаверяющим центром сертификации, чтобы прокси мог перехватить HTTPS движение.

Производственные хосты никогда не должны использовать недоверенные сертификаты по очевидным причинам.

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

Рекомендуемый способ принять недоверенные сертификаты для тестирования - импортировать сертификат центра сертификации (CA), который подписал сертификат, на симуляторе iOS или устройстве iOS. Я написал короткую запись в блоге, в которой демонстрируется, как это сделать с помощью симулятора iOS:

прием ненадежных сертификатов с помощью симулятора ios

В дополнение к принятому ответу для большей безопасности вы можете добавить свой сертификат сервера или собственный сертификат корневого центра сертификации в цепочку для ключей ( /questions/9016816/ios-predvaritelno-ustanovit-ssl-sertifikat-v-svyazke-klyuchej-programmno/9016835#9016835), однако выполнение одного этого действия не приведет к NSURLConnection аутентифицируйте свой самоподписанный сервер автоматически. Вам все еще нужно добавить приведенный ниже код в ваш делегат NSURLConnection, он скопирован из примера кода Apple AdvancedURLConnections, и вам нужно добавить два файла (Credentials.h, Credentials.m) из примера кода Apple в ваши проекты.

- (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]) {
//        if ([trustedHosts containsObject:challenge.protectionSpace.host])

    OSStatus                err;
    NSURLProtectionSpace *  protectionSpace;
    SecTrustRef             trust;
    SecTrustResultType      trustResult;
    BOOL                    trusted;

    protectionSpace = [challenge protectionSpace];
    assert(protectionSpace != nil);

    trust = [protectionSpace serverTrust];
    assert(trust != NULL);
    err = SecTrustEvaluate(trust, &trustResult);
    trusted = (err == noErr) && ((trustResult == kSecTrustResultProceed) || (trustResult == kSecTrustResultUnspecified));

    // If that fails, apply our certificates as anchors and see if that helps.
    //
    // It's perfectly acceptable to apply all of our certificates to the SecTrust
    // object, and let the SecTrust object sort out the mess.  Of course, this assumes
    // that the user trusts all certificates equally in all situations, which is implicit
    // in our user interface; you could provide a more sophisticated user interface
    // to allow the user to trust certain certificates for certain sites and so on).

    if ( ! trusted ) {
        err = SecTrustSetAnchorCertificates(trust, (CFArrayRef) [Credentials sharedCredentials].certificates);
        if (err == noErr) {
            err = SecTrustEvaluate(trust, &trustResult);
        }
        trusted = (err == noErr) && ((trustResult == kSecTrustResultProceed) || (trustResult == kSecTrustResultUnspecified));
    }
    if(trusted)
        [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
}

[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}

NSURLRequest имеет закрытый метод setAllowsAnyHTTPSCertificate:forHost:, который будет делать именно то, что вы хотите. Вы можете определить allowsAnyHTTPSCertificateForHost: метод на NSURLRequest через категорию, и установите его для возврата YES для хоста, который вы хотите переопределить.

В iOS 9 SSL-соединения не будут работать для всех недействительных или самозаверяющих сертификатов. Это поведение по умолчанию для новой функции App Transport Security в iOS 9.0 или более поздней версии, а также в OS X 10.11 и более поздней версии.

Вы можете изменить это поведение в Info.plist, установив NSAllowsArbitraryLoads в YES в NSAppTransportSecurity толковый словарь. Однако я рекомендую переопределить этот параметр только для целей тестирования.

Для получения информации см. App Transport Technote здесь.

Я не могу взять кредит на это, но этот, который я нашел, работал очень хорошо для моих нужд. shouldAllowSelfSignedCert мой BOOL переменная. Просто добавьте в свой NSURLConnection делегировать, и вы должны быть готовы к быстрому обходу для каждого соединения.

- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)space {
     if([[space authenticationMethod] isEqualToString:NSURLAuthenticationMethodServerTrust]) {
          if(shouldAllowSelfSignedCert) {
               return YES; // Self-signed cert will be accepted
          } else {
               return NO;  // Self-signed cert will be rejected
          }
          // Note: it doesn't seem to matter what you return for a proper SSL cert
          //       only self-signed certs
     }
     // If no other authentication is required, return NO for everything else
     // Otherwise maybe YES for NSURLAuthenticationMethodDefault and etc.
     return NO;
}

Временное решение для категории, опубликованное Натаном де Врисом, пройдет частные проверки API AppStore и будет полезно в тех случаях, когда вы не можете контролировать NSUrlConnection объект. Одним из примеров является NSXMLParser откроет URL, который вы указали, но не NSURLRequest или же NSURLConnection,

В iOS 4 обходной путь все еще работает, но только на устройстве симулятор не вызывает allowsAnyHTTPSCertificateForHost: метод больше.

Вы должны использовать NSURLConnectionDelegate разрешить HTTPS-соединения и есть новые обратные вызовы с iOS8.

Запрещены:

connection:canAuthenticateAgainstProtectionSpace:
connection:didCancelAuthenticationChallenge:
connection:didReceiveAuthenticationChallenge:

Вместо этого вам нужно объявить:

connectionShouldUseCredentialStorage: - Отправляется, чтобы определить, должен ли загрузчик URL-адресов использовать хранилище учетных данных для проверки подлинности соединения.

connection:willSendRequestForAuthenticationChallenge: - Сообщает делегату, что соединение отправит запрос на вызов аутентификации.

С willSendRequestForAuthenticationChallenge ты можешь использовать challenge как вы сделали с устаревшими методами, например:

// Trusting and not trusting connection to host: Self-signed certificate
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];

Я опубликовал некоторый основной код (основанный на чьей-либо работе, который я отмечаю), который позволяет вам правильно проходить аутентификацию на основе сгенерированного сертификата (и как получить бесплатный сертификат - см. Комментарии внизу Cocoanetics)

Мой код здесь github

Вы можете использовать этот код

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

использование -connection:willSendRequestForAuthenticationChallenge: вместо этих устаревших методов

Запрещены:

-(BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace  
-(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge 
-(void)connection:(NSURLConnection *)connection didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge

Если вы хотите продолжать использовать sendSynchronousRequest, я работаю в этом решении:

FailCertificateDelegate *fcd=[[FailCertificateDelegate alloc] init];

NSURLConnection *c=[[NSURLConnection alloc] initWithRequest:request delegate:fcd startImmediately:NO];
[c setDelegateQueue:[[NSOperationQueue alloc] init]];
[c start];    
NSData *d=[fcd getData];

Вы можете увидеть это здесь: Синхронное соединение SSL Objective-C

С AFNetworking я успешно использовал https веб-сервис с приведенным ниже кодом,

NSString *aStrServerUrl = WS_URL;

// Initialize AFHTTPRequestOperationManager...
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
manager.responseSerializer = [AFJSONResponseSerializer serializer];

[manager.requestSerializer setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
manager.securityPolicy.allowInvalidCertificates = YES; 
[manager POST:aStrServerUrl parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject)
{
    successBlock(operation, responseObject);

} failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
    errorBlock(operation, error);
}];
Другие вопросы по тегам