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

300

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

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

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

erotsppa
источник

Ответы:

415

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

Гордон Хенриксен
источник
31
Большое спасибо, все работает отлично. Просто удалите два if и оставьте только часть useCendential в обратном вызове didReceiveAuthentificationChallenge, если вы хотите принять любой https-сайт.
Йонель
19
что такое доверенный хост, где n как определяется объект
Ameya
7
Амея, это будет NSArray NSString объектов. Строки - это имена хостов, например @ "google.com".
Уильям Денисс
19
Этот код работает хорошо. Но обратите внимание, что весь смысл наличия действительных сертификатов заключается в предотвращении атак «человек посередине». Так что будьте внимательны, если вы используете этот код, кто-то может подделать так называемый «доверенный хост». Вы по-прежнему получаете функции шифрования данных SSL, но теряете функции проверки идентификации хоста.
Уильям Денисс
42
Эти методы в настоящее время считаются устаревшими с iOS 5.0 и Mac OS X 10.6. -(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challengeМетод должен быть использован вместо.
Андрей Р.
36

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

Натан де Врис
источник
2
Тим, возможно, ты все равно захочешь использовать асинхронность по другим причинам (например, возможность показать индикатор выполнения), я нахожу для всех, кроме самых простых запросов, то, что я делаю. Поэтому, возможно, вам следует просто внедрить Async сейчас, а потом сэкономить.
Уильям Деннисс
Посмотрите это для реализации (но используйте [r setValidatesSecureCertificate: NO];): stackoverflow.com/questions/7657786/…
Сэм Бродкин
Извините, что снова поднял эту тему. Но с тех пор в iOS 5 появились функции ARC. Как я могу сделать эту работу сейчас?
Мелвин Лай
Не могли бы вы проверить это: stackoverflow.com/q/56627757/1364053
nr5
33

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

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

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

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

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

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

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

user890103
источник
1
Офигенный мужик. Я согласен, так легко забыть об отключении этой специальной логики приложения для принятия любого ненадежного сертификата.
Томаш
«В идеале, должно быть только два сценария, когда приложению iOS потребуется принять недоверенный сертификат». - Как насчет отклонения «заявленного» хорошего сертификата при закреплении сертификата? Confer: Dignotar (pwn'd) и Trustwave (известность MitM).
jww
Полностью согласен с вашим утверждением о том, что вы забыли удалить код. Ирония заключается в том, что сделать это изменение в коде намного проще, чем заставить симулятор принимать самозаверяющие сертификаты.
devios1
12

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

Натан де Врис
источник
Применяются обычные предостережения о недокументированных API ... но приятно знать, что это возможно.
Стивен Дарлингтон
Да, абсолютно. Я добавил еще один ответ, который не предусматривает использование частных API.
Натан де Врис
Это работает, когда вы используете "NSURLConnection sendSynchronousRequest:"?
Тим Бют
11

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

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

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

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

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

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

johnnieb
источник
Единственное решение сработало для меня, у меня нет способа изменить фреймворк Firebase, чтобы удовлетворить мои потребности, и это решило его, спасибо!
Ицхак
Теперь я увидел, что Google запрашивает NSAllowArbitraryLoads = YES для Admob (в Firebase). firebase.google.com/docs/admob/ios/ios9
Ицхак
6

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

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

Алекс Судзуки
источник
6

Вы должны использовать, 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];
ricardopereira
источник
Не могли бы вы проверить это: stackoverflow.com/q/56627757/1364053
№5
3

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

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

Дэвид Х
источник
Не могли бы вы проверить это: stackoverflow.com/q/56627757/1364053
nr5
2

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

jgorozco
источник
1

С 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);
}];
CJD
источник
1

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

-(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
Вайбхав Шарма
источник