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

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

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

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

Ответ 1

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

Ответ 2

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

Ответ 3

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

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

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

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

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

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

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

Ответ 4

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

Ответ 5

Я не могу поверить в это, но этот, который я нашел, работал очень хорошо для моих нужд. 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;
}

Ответ 6

Чтобы дополнить принятый ответ, для обеспечения большей безопасности вы можете добавить свой сертификат сервера или собственный корневой сертификат ЦС в цепочку ключей (fooobar.com/questions/24877/...), однако это само по себе не заставит 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 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];
}

Ответ 7

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

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

введите описание изображения здесь

Информацию см. в App Transport Technote здесь.

Ответ 8

Обходное решение категории, опубликованное Nathan de Vries, будет проходить проверку Private API AppStore и полезно в тех случаях, когда вы не контролируете объект NSUrlConnection. Одним из примеров является NSXMLParser, который откроет указанный вами URL-адрес, но не выставляет NSURLRequest или NSUrlConnection.

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

Ответ 9

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

Ответ 10

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

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

Ответ 11

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

вы можете увидеть его здесь: Objective-C Синхронное подключение SSL

Ответ 12

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

Ответ 13

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

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