Как использовать 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". есть ли способ установить его для приема соединений в любом случае (так же, как в браузере вы можете нажать accept) или способ обойти его?

13 285

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: может отправить свое сообщение, чтобы бросить вызов.отправитель (намного) позже, после представления диалогового окна пользователю при необходимости и т. д.

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

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

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

сценарий B: вы Проксируете HTTPS трафик, используя MITM Proxy like Burp Suite, Fiddler, OWASP ZAP, etc. Прокси вернет сертификат, подписанный собственной подписью центра сертификации, поэтому, что прокси-сервер способен улавливать HTTPS трафик.

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

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

рекомендуемый способ принять недоверенные сертификаты для целей тестирования-это импорт сертификата Центра сертификации(ЦС), который подписал сертификат на ваш симулятор iOS или устройство iOS. Я написал быстрый пост в блоге, который демонстрирует, как это сделать, что симулятор iOS по адресу:

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

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

чтобы дополнить принятый ответ, для гораздо лучшей безопасности вы можете добавить свой сертификат сервера или свой собственный сертификат корневого ЦС в keychain( https://stackoverflow.com/a/9941559/1432048), однако выполнение этого в одиночку не заставит NSURLConnection автоматически аутентифицировать ваш самозаверяющий сервер. Вам все равно нужно добавить приведенный ниже код в делегат NSURLConnection, он скопирован из примера кода Apple AdvancedURLConnections и нужно добавить две файлы(учетные данные.h, верительные грамоты.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];
}

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

- (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;
}

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

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

enter image description here

дополнительные сведения см. В разделе приложение Транспорт комментарием здесь.

обходной путь категории, опубликованный Натаном де Фрисом, пройдет проверку частного API AppStore и будет полезен в тех случаях, когда у вас нет контроля над

вы должны использовать 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];

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

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

Если вы хотите продолжать использовать 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];

посмотреть можно здесь: цель-C SSL Синхронное соединение

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

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

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

-(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