Wie verwende ich NSURLConnection, um eine Verbindung mit SSL für ein nicht vertrauenswürdiges Zertifikat herzustellen?

300

Ich habe den folgenden einfachen Code, um eine Verbindung zu einer SSL-Webseite herzustellen

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

Außer es gibt einen Fehler, wenn das Zertifikat selbstsigniert ist. Error Domain=NSURLErrorDomain Code=-1202 UserInfo=0xd29930 "untrusted server certificate".Gibt es eine Möglichkeit, es so einzustellen, dass Verbindungen trotzdem akzeptiert werden (genau wie in einem Browser, in dem Sie auf Akzeptieren klicken können) oder es zu umgehen?

Erotik
quelle

Antworten:

415

Es gibt eine unterstützte API, um dies zu erreichen! Fügen Sie Ihrem NSURLConnectionDelegierten so etwas hinzu:

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

Beachten Sie, dass connection:didReceiveAuthenticationChallenge:die Nachricht (viel) später an Challenge.sender gesendet werden kann, nachdem dem Benutzer bei Bedarf ein Dialogfeld usw. angezeigt wurde.

Gordon Henriksen
quelle
31
Vielen Dank, es funktioniert perfekt. Entfernen Sie einfach die beiden ifs und behalten Sie nur den useCendential-Teil im didReceiveAuthentificationChallenge-Rückruf bei, wenn Sie eine https-Site akzeptieren möchten.
Yonel
19
Was ist ein TrustedHosts, wo und wie ist das Objekt definiert
Ameya
7
Ameya, es wäre ein NSArray von NSString-Objekten. Die Zeichenfolgen sind die Hostnamen wie @ "google.com".
William Denniss
19
Dieser Code funktioniert gut. Beachten Sie jedoch, dass der springende Punkt bei gültigen Zertifikaten darin besteht, Man-in-the-Middle-Angriffe zu verhindern. Wenn Sie diesen Code verwenden, kann jemand den sogenannten "vertrauenswürdigen Host" fälschen. Sie erhalten weiterhin die Datenverschlüsselungsfunktionen von SSL, verlieren jedoch die Validierungsfunktionen für die Hostidentifizierung.
William Denniss
42
Diese Methoden gelten ab iOS 5.0 und Mac OS X 10.6 als veraltet. Die -(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challengeMethode sollte stattdessen verwendet werden.
Andrew R.
36

Wenn Sie nicht bereit (oder nicht in der Lage) sind, private APIs zu verwenden, gibt es eine Open-Source-Bibliothek (BSD-Lizenz) namens ASIHTTPRequest , die einen Wrapper für die untere Ebene bereitstellt CFNetwork APIs. Sie haben kürzlich die Möglichkeit eingeführt, die HTTPS connectionsVerwendung von selbstsignierten oder nicht vertrauenswürdigen Zertifikaten mit der -setValidatesSecureCertificate:API zuzulassen . Wenn Sie nicht die gesamte Bibliothek abrufen möchten, können Sie die Quelle als Referenz verwenden, um dieselbe Funktionalität selbst zu implementieren.

Nathan de Vries
quelle
2
Tim, vielleicht möchten Sie Async ohnehin aus anderen Gründen verwenden (z. B. um einen Fortschrittsbalken anzeigen zu können). Ich finde, dass dies für alle außer den einfachsten Anfragen der Weg ist, den ich gehe. Vielleicht sollten Sie Async jetzt einfach implementieren und den Aufwand später sparen.
William Denniss
Siehe dies für die Implementierung (aber verwenden Sie [r setValidatesSecureCertificate: NO];): stackoverflow.com/questions/7657786/…
Sam Brodkin
Entschuldigung, dass ich dieses Thema wieder aufgegriffen habe. Aber seit iOS 5 wurden die ARC-Funktionen eingeführt. Wie kann ich das jetzt zum Laufen bringen?
Melvin Lai
Könnten Sie dies bitte überprüfen: stackoverflow.com/q/56627757/1364053
nr5
33

Im Idealfall sollte es nur zwei Szenarien geben, in denen eine iOS-Anwendung ein nicht vertrauenswürdiges Zertifikat akzeptieren muss.

Szenario A: Sie sind mit einer Testumgebung verbunden, die ein selbstsigniertes Zertifikat verwendet.

Szenario B: Sie stellen HTTPSDatenverkehr mithilfe eines MITM Proxy like Burp Suite, Fiddler, OWASP ZAP, etc.Proxys bereit. Die Proxies geben ein von einer selbstsignierten Zertifizierungsstelle signiertes Zertifikat zurück, damit der Proxy HTTPSDatenverkehr erfassen kann .

Produktionshosts sollten aus offensichtlichen Gründen niemals nicht vertrauenswürdige Zertifikate verwenden .

Wenn der iOS-Simulator zu Testzwecken ein nicht vertrauenswürdiges Zertifikat akzeptieren muss, wird dringend empfohlen, die Anwendungslogik nicht zu ändern, um die von den NSURLConnectionAPIs bereitgestellte integrierte Zertifikatvalidierung zu deaktivieren . Wenn die Anwendung für die Öffentlichkeit freigegeben wird, ohne diese Logik zu entfernen, ist sie anfällig für Man-in-the-Middle-Angriffe.

Die empfohlene Methode zum Akzeptieren nicht vertrauenswürdiger Zertifikate zu Testzwecken besteht darin, das Zertifikat der Zertifizierungsstelle (Certificate Authority, CA), das das Zertifikat signiert hat, auf Ihren iOS-Simulator oder Ihr iOS-Gerät zu importieren. Ich habe einen kurzen Blog-Beitrag geschrieben, der zeigt, wie man das mit einem iOS-Simulator macht:

Akzeptieren nicht vertrauenswürdiger Zertifikate mit dem iOS-Simulator

user890103
quelle
1
Super Zeug Mann. Ich stimme zu, es ist so leicht zu vergessen, diese spezielle App-Logik zu deaktivieren, um nicht vertrauenswürdige Zertifikate zu akzeptieren.
Tomasz
"Idealerweise sollte es nur zwei Szenarien geben, in denen eine iOS-Anwendung ein nicht vertrauenswürdiges Zertifikat akzeptieren muss." - Wie wäre es, wenn Sie ein "beanspruchtes" gutes Zertifikat ablehnen, wenn Sie ein Zertifikat anheften? Konferenz: Dignotar (pwn'd) und Trustwave (MitM Ruhm).
JWW
Stimmen Sie voll und ganz Ihrer Aussage zu, dass Sie vergessen haben, den Code zu entfernen. Die Ironie ist, dass es viel einfacher ist, diese Änderung im Code vorzunehmen, als den Simulator dazu zu bringen, selbstsignierte Zertifikate zu akzeptieren.
Devios1
12

NSURLRequesthat eine private Methode namens setAllowsAnyHTTPSCertificate:forHost:, die genau das tut, was Sie möchten. Sie können die allowsAnyHTTPSCertificateForHost:Methode NSURLRequestüber eine Kategorie definieren und festlegen, dass sie YESfür den Host zurückgegeben wird, den Sie überschreiben möchten.

Nathan de Vries
quelle
Es gelten die üblichen Vorbehalte gegen undokumentierte APIs ... aber gut zu wissen, dass dies möglich ist.
Stephen Darlington
Ja, absolut. Ich habe eine weitere Antwort hinzugefügt, bei der keine privaten APIs verwendet werden.
Nathan de Vries
Funktioniert das, wenn Sie "NSURLConnection sendSynchronousRequest:" verwenden?
Tim Büthe
11

Um die akzeptierte Antwort zu ergänzen, können Sie aus Sicherheitsgründen Ihr Serverzertifikat oder Ihr eigenes Zertifikat der Stammzertifizierungsstelle zum Schlüsselbund hinzufügen ( https://stackoverflow.com/a/9941559/1432048 ). Dies allein führt jedoch nicht zu einer NSURLConnection Authentifizieren Sie Ihren selbstsignierten Server automatisch. Sie müssen Ihrem NSURLConnection-Delegaten weiterhin den folgenden Code hinzufügen, der aus dem Apple-Beispielcode AdvancedURLConnections kopiert wurde , und Sie müssen Ihren Projekten zwei Dateien (Credentials.h, Credentials.m) aus dem Apple-Beispielcode hinzufügen.

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

Ich kann das nicht gutschreiben, aber dieses, das ich gefunden habe, hat wirklich gut für meine Bedürfnisse funktioniert. shouldAllowSelfSignedCertist meine BOOLVariable. Fügen Sie einfach Ihrem NSURLConnectionDelegierten hinzu, und Sie sollten sich auf eine schnelle Umgehung pro Verbindung einstellen.

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

In iOS 9 schlagen SSL-Verbindungen für alle ungültigen oder selbstsignierten Zertifikate fehl. Dies ist das Standardverhalten der neuen App Transport Security- Funktion in iOS 9.0 oder höher und unter OS X 10.11 und höher.

Sie können dieses Verhalten im Wörterbuch überschreiben Info.plist, indem Sie NSAllowsArbitraryLoadses YESim NSAppTransportSecurityWörterbuch auf setzen. Ich empfehle jedoch, diese Einstellung nur zu Testzwecken zu überschreiben.

Geben Sie hier die Bildbeschreibung ein

Informationen finden Sie unter App Transport Technote hier .

Johnnieb
quelle
Die einzige Lösung, die für mich funktioniert hat, ich habe keine Möglichkeit, das Firebase-Framework so zu ändern, dass es meinen Anforderungen entspricht. Das hat es gelöst, danke!
Yitzchak
Jetzt habe ich gesehen, dass Google nach NSAllowArbitraryLoads = YES für Admob (in Firebase) fragt. firebase.google.com/docs/admob/ios/ios9
Yitzchak
6

Die von Nathan de Vries veröffentlichte Problemumgehung für Kategorien besteht die privaten API-Prüfungen des AppStore und ist in Fällen hilfreich, in denen Sie keine Kontrolle über das NSUrlConnectionObjekt haben. Ein Beispiel ist NSXMLParserdas Öffnen der von Ihnen angegebenen URL, ohne das NSURLRequestoder NSURLConnection.

In iOS 4 scheint die Problemumgehung immer noch zu funktionieren, aber nur auf dem Gerät ruft der Simulator die allowsAnyHTTPSCertificateForHost:Methode nicht mehr auf.

Alex Suzuki
quelle
6

Du musst benutzen NSURLConnectionDelegate , um HTTPS-Verbindungen zuzulassen, und es gibt neue Rückrufe mit iOS8.

Veraltet:

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

Stattdessen müssen Sie Folgendes deklarieren:

connectionShouldUseCredentialStorage: - Wird gesendet, um zu bestimmen, ob der URL-Loader den Speicher für Anmeldeinformationen zur Authentifizierung der Verbindung verwenden soll.

connection:willSendRequestForAuthenticationChallenge: - Weist den Delegaten an, dass die Verbindung eine Anforderung für eine Authentifizierungsaufforderung sendet.

Mit willSendRequestForAuthenticationChallengekönnen challengeSie wie mit den veralteten Methoden verwenden, zum Beispiel:

// 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
quelle
Könnten Sie dies bitte überprüfen: stackoverflow.com/q/56627757/1364053
nr5
3

Ich habe einen Kerncode veröffentlicht (basierend auf der Arbeit eines anderen, den ich notiere), mit dem Sie sich ordnungsgemäß bei einem selbst erstellten Zertifikat authentifizieren können (und wie Sie ein kostenloses Zertifikat erhalten - siehe Kommentare unten in Cocoanetics ).

Mein Code ist hier Github

David H.
quelle
Könnten Sie dies bitte überprüfen: stackoverflow.com/q/56627757/1364053
nr5
2

Wenn Sie sendSynchronousRequest weiterhin verwenden möchten, arbeite ich in dieser Lösung:

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

Sie können es hier sehen: Objective-C SSL Synchronous Connection

jgorozco
quelle
1

Mit AFNetworking habe ich den https-Webservice mit dem folgenden Code erfolgreich genutzt.

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
quelle
1

Sie können diesen Code verwenden

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

Verwenden Sie -connection:willSendRequestForAuthenticationChallenge:anstelle dieser veralteten Methoden

Veraltet:

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