Jak używać NSURLConnection do łączenia się z SSL dla niezaufanego certyfikatu?

300

Mam następujący prosty kod do połączenia ze stroną SSL

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

Tyle że daje błąd, jeśli certyfikat jest samopodpisany. Error Domain=NSURLErrorDomain Code=-1202 UserInfo=0xd29930 "untrusted server certificate".Czy istnieje sposób, aby mimo to akceptować połączenia (tak jak w przeglądarce, możesz nacisnąć przycisk akceptuj) lub sposób na ominięcie go?

erotsppa
źródło

Odpowiedzi:

415

Obsługiwane jest obsługiwane API! Dodaj coś takiego do swojego NSURLConnectiondelegata:

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

Pamiętaj, że connection:didReceiveAuthenticationChallenge:może wysłać swoją wiadomość do challenge.sender (znacznie) później, po wyświetleniu użytkownikowi okna dialogowego, jeśli to konieczne itp.

Gordon Henriksen
źródło
31
Wielkie dzięki, działa idealnie. Wystarczy usunąć dwa ifs i zachować tylko część useCendential w wywołaniu zwrotnym didReceiveAuthentificationChallenge, jeśli chcesz zaakceptować dowolną witrynę https.
yonel
19
czym jest trustHosts, gdzie n jak zdefiniowany jest obiekt
Ameya
7
Ameya, byłby to NSArray obiektów NSString. Ciągi to nazwy hostów, takie jak @ „google.com”.
William Denniss,
19
Ten kod działa dobrze. Pamiętaj jednak, że celem posiadania ważnych certyfikatów jest zapobieganie atakom typu man-in-the-middle. Pamiętaj więc, że jeśli użyjesz tego kodu, ktoś może sfałszować tak zwany „zaufany host”. Nadal masz funkcje szyfrowania danych SSL, ale tracisz funkcje sprawdzania poprawności identyfikatora hosta.
William Denniss,
42
Te metody są obecnie uważane za przestarzałe od iOS 5.0 i Mac OS X 10.6. -(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challengeSposób powinien być używany.
Andrew R.
36

Jeśli nie chcesz (lub nie możesz) korzystać z prywatnych interfejsów API, istnieje biblioteka open source (licencja BSD) o nazwie ASIHTTPRequest, która zapewnia opakowanie wokół niższego poziomu CFNetwork APIs. Niedawno wprowadzono możliwość zezwalania na HTTPS connectionsużywanie certyfikatów z podpisem własnym lub niezaufanymi w -setValidatesSecureCertificate:interfejsie API. Jeśli nie chcesz pobierać całej biblioteki, możesz użyć źródła jako odniesienia do samodzielnego wdrożenia tej samej funkcjonalności.

Nathan de Vries
źródło
2
Tim, może i tak chcesz użyć asynchronizacji z innych powodów (takich jak możliwość wyświetlenia paska postępu), znajduję dla wszystkich prócz najprostszych żądań, które tak robię. Może więc powinieneś wdrożyć teraz Async i zaoszczędzić kłopotów później.
William Denniss
Zobacz to dla implementacji (ale użyj [r setValidatesSecureCertificate: NO];): stackoverflow.com/questions/7657786/...
Sam Brodkin
Przepraszam, że przywróciłem ten temat. Ale odkąd iOS 5 wprowadził funkcje ARC. Jak mogę to teraz zrobić?
Melvin Lai
Czy możesz to sprawdzić: stackoverflow.com/q/56627757/1364053
nr5
33

W idealnym przypadku powinny istnieć tylko dwa scenariusze, w których aplikacja iOS musiałaby zaakceptować niezaufany certyfikat.

Scenariusz A: Masz połączenie ze środowiskiem testowym, które korzysta z certyfikatu z podpisem własnym.

Scenariusz B: Serwer proxy wykonuje HTTPSruch przy użyciu MITM Proxy like Burp Suite, Fiddler, OWASP ZAP, etc.serwera proxy Zwraca certyfikat podpisany przez samopodpisany urząd certyfikacji, aby serwer proxy mógł przechwytywać HTTPSruch.

Hosty produkcyjne nigdy nie powinny używać niezaufanych certyfikatów z oczywistych powodów .

Jeśli potrzebujesz, aby symulator iOS zaakceptował niezaufany certyfikat do celów testowych, zdecydowanie nie należy zmieniać logiki aplikacji, aby wyłączyć wbudowane sprawdzanie poprawności certyfikatu zapewniane przez NSURLConnectioninterfejsy API. Jeśli aplikacja zostanie udostępniona publicznie bez usunięcia tej logiki, będzie podatna na ataki typu man-in-the-middle.

Zalecanym sposobem akceptowania niezaufanych certyfikatów do celów testowych jest zaimportowanie certyfikatu urzędu certyfikacji (CA), który podpisał certyfikat na symulatorze lub urządzeniu z systemem iOS. Napisałem szybki post na blogu, który pokazuje, jak to zrobić, na którym znajduje się symulator iOS:

akceptowanie niezaufanych certyfikatów za pomocą symulatora ios

użytkownik890103
źródło
1
Niesamowite rzeczy stary. Zgadzam się, tak łatwo zapomnieć o wyłączeniu tej specjalnej logiki aplikacji, aby zaakceptować niezaufany certyfikat.
Tomasz
„W idealnym przypadku powinny istnieć tylko dwa scenariusze, w których aplikacja na iOS musiałaby zaakceptować niezaufany certyfikat”. - A co powiesz na odrzucenie dobrego „certyfikowanego” certyfikatu podczas przypinania certyfikatu? Konferencja: Dignotar (pwn'd) i Trustwave (sława MitM).
jww
Całkowicie zgadzam się z oświadczeniem dotyczącym zapomnienia o usunięciu kodu. Ironią jest to, że o wiele łatwiej jest wprowadzić tę zmianę w kodzie niż zmusić symulator do akceptowania certyfikatów z podpisem własnym.
devios1
12

NSURLRequestma prywatną metodę o nazwie setAllowsAnyHTTPSCertificate:forHost:, która zrobi dokładnie to, co chcesz. Możesz zdefiniować allowsAnyHTTPSCertificateForHost:metodę NSURLRequestza pomocą kategorii i ustawić ją tak, aby zwracała się YESpo hoście, który chcesz zastąpić.

Nathan de Vries
źródło
Obowiązują zwykłe zastrzeżenia dotyczące nieudokumentowanych interfejsów API ... ale dobrze wiedzieć, że jest to możliwe.
Stephen Darlington,
Tak oczywiście. Dodałem inną odpowiedź, która nie wymaga użycia prywatnych interfejsów API.
Nathan de Vries,
Czy to działa, gdy używasz „NSURLConnection sendSynchronousRequest:”?
Tim Büthe,
11

W celu uzupełnienia przyjętej odpowiedzi, dla znacznie większego bezpieczeństwa, możesz dodać swój certyfikat serwera lub własny certyfikat głównego urzędu certyfikacji do pęku kluczy ( https://stackoverflow.com/a/9941559/1432048 ), ale zrobienie tego samemu nie spowoduje NSURLConnection automatycznie uwierzytelnij samopodpisany serwer. Nadal musisz dodać poniższy kod do delegata NSURLConnection, jest on kopiowany z przykładowego kodu Apple AdvancedURLConnections i musisz dodać dwa pliki (Credentials.h, Credentials.m) z przykładowego kodu Apple do swoich projektów.

- (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];
}
Xiang
źródło
10

Nie mogę tego przypisać, ale ten, który okazał się bardzo skuteczny dla moich potrzeb. shouldAllowSelfSignedCertjest moją BOOLzmienną. Po prostu dodaj do swojego NSURLConnectiondelegata i powinieneś się rozerwać, aby szybko ominąć połączenie dla każdego połączenia.

- (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
źródło
10

W iOS 9 połączenia SSL nie będą działać dla wszystkich nieprawidłowych lub samopodpisanych certyfikatów. Jest to domyślne zachowanie nowej funkcji App Transport Security w systemie iOS 9.0 lub nowszym oraz w OS X 10.11 i nowszych.

Możesz zastąpić to zachowanie w Info.plist, ustawiając NSAllowsArbitraryLoadsna YESw NSAppTransportSecuritysłowniku. Zalecam jednak zastąpienie tego ustawienia wyłącznie do celów testowych.

wprowadź opis zdjęcia tutaj

Aby uzyskać więcej informacji, zobacz Technote aplikacji transportu tutaj .

johnnieb
źródło
Jedyne rozwiązanie działało dla mnie, nie mam możliwości zmiany frameworka Firebase w celu dostosowania go do moich potrzeb, które rozwiązały, dzięki!
Yitzchak,
Teraz widziałem, że Google prosi o NSAllowArbitraryLoads = TAK dla Admob (w Firebase). firebase.google.com/docs/admob/ios/ios9
Yitzchak,
6

Obejście kategorii opublikowane przez Nathana de Vriesa przejdzie testy prywatnego interfejsu API AppStore i jest przydatne w przypadkach, gdy nie masz kontroli nad NSUrlConnectionobiektem. Jednym z przykładów jest NSXMLParserotwarcie podanego adresu URL, ale nie ujawnienie NSURLRequestlubNSURLConnection .

W iOS 4 obejście nadal wydaje się działać, ale tylko na urządzeniu Symulator nie wywołuje allowsAnyHTTPSCertificateForHost:już tej metody.

Alex Suzuki
źródło
6

Musisz użyć, NSURLConnectionDelegateaby zezwolić na połączenia HTTPS, aw iOS8 pojawiły się nowe połączenia zwrotne.

Przestarzałe:

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

Zamiast tego musisz zadeklarować:

connectionShouldUseCredentialStorage: - Wysłane w celu ustalenia, czy moduł ładujący URL powinien używać magazynu referencji do uwierzytelniania połączenia.

connection:willSendRequestForAuthenticationChallenge: - Mówi delegatowi, że połączenie wyśle ​​żądanie uwierzytelnienia.

Dzięki willSendRequestForAuthenticationChallengemożesz używać challengetak jak w przypadku przestarzałych metod, na przykład:

// 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
źródło
Czy możesz to sprawdzić: stackoverflow.com/q/56627757/1364053
nr5
3

Zamieściłem trochę kodu gist (na podstawie pracy innej osoby, którą odnotowuję), która pozwala poprawnie uwierzytelnić się na podstawie samodzielnie wygenerowanego certyfikatu (i jak uzyskać bezpłatny certyfikat - patrz komentarze na dole Cocoanetics )

Mój kod jest tutaj github

David H.
źródło
Czy możesz to sprawdzić: stackoverflow.com/q/56627757/1364053
nr5
2

Jeśli chcesz nadal używać sendSynchronousRequest, pracuję w tym rozwiązaniu:

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

możesz to zobaczyć tutaj: Synchroniczne połączenie SSL celu-C

jgorozco
źródło
1

Dzięki AFNetworking z powodzeniem korzystałem z usługi https z poniższym kodem,

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
źródło
1

Możesz użyć tego kodu

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

Posługiwać się -connection:willSendRequestForAuthenticationChallenge: zamiast tych przestarzałych metod

Przestarzałe:

-(BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace  
-(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge 
-(void)connection:(NSURLConnection *)connection didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
Vaibhav Sharma
źródło