Come utilizzare NSURLConnection per connettersi con SSL per un certificato non attendibile?


300

Ho il seguente semplice codice per connettermi a una pagina Web SSL

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

Tranne che dà un errore se il certificato è autofirmato Error Domain=NSURLErrorDomain Code=-1202 UserInfo=0xd29930 "untrusted server certificate".C'è un modo per impostarlo per accettare le connessioni comunque (proprio come in un browser è possibile premere accetta) o un modo per bypassarlo?

Risposte:


415

C'è un'API supportata per raggiungere questo obiettivo! Aggiungi qualcosa del genere al tuo NSURLConnectiondelegato:

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

Nota che connection:didReceiveAuthenticationChallenge:puoi inviare il suo messaggio a challenge.sender (molto) in seguito, dopo aver presentato una finestra di dialogo all'utente, se necessario, ecc.


31
Grazie mille, funziona perfettamente. Basta rimuovere i due if e conservare solo la parte useCendential nel callback didReceiveAuthentificationChallenge se si desidera accettare qualsiasi sito https.
yonel,

19
cos'è un host di fiducia, dove n come viene definito l'oggetto
Ameya,

7
Ameya, sarebbe un NSArray di oggetti NSString. Le stringhe sono i nomi host come @ "google.com".
William Denniss,

19
Questo codice funziona bene. Ma nota che l'intero punto di avere certificati validi è prevenire attacchi man-in-the-middle. Quindi fai attenzione se usi questo codice, qualcuno può falsificare il cosiddetto "host fidato". Ottieni ancora le funzionalità di crittografia dei dati di SSL ma perdi l'host per identificare le funzionalità di convalida.
William Denniss,

42
Questi metodi sono ora considerati obsoleti a partire da iOS 5.0 e Mac OS X 10.6. Si -(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challengedovrebbe usare invece il metodo.
Andrew R.,

36

Se non sei disposto (o non riesci) a utilizzare API private, esiste una libreria open source (licenza BSD) chiamata ASIHTTPRequest che fornisce un wrapper al livello inferiore CFNetwork APIs. Di recente hanno introdotto la possibilità di consentire l' HTTPS connectionsutilizzo di certificati autofirmati o non attendibili con l' -setValidatesSecureCertificate:API. Se non si desidera inserire l'intera libreria, è possibile utilizzare l'origine come riferimento per l'implementazione della stessa funzionalità.


2
Tim, potresti trovarti a voler usare l'asincronizzazione per altri motivi (come essere in grado di mostrare una barra di avanzamento), trovo per tutti, ma la più semplice delle richieste è la mia strada. Quindi forse dovresti semplicemente implementare Async ora e salvare la seccatura in seguito.
William Denniss,

Vedi questo per l'implementazione (ma usa [r setValidatesSecureCertificate: NO];): stackoverflow.com/questions/7657786/…
Sam Brodkin,

Mi dispiace di aver ripristinato questo argomento. Ma da quando iOS 5 ha introdotto le funzionalità ARC. Come posso farlo funzionare ora?
Melvin Lai,

Potresti controllare questo: stackoverflow.com/q/56627757/1364053
nr5

33

Idealmente, ci dovrebbero essere solo due scenari in cui un'applicazione iOS dovrebbe accettare un certificato non attendibile.

Scenario A: si è connessi a un ambiente di test che utilizza un certificato autofirmato.

Scenario B: si sta eseguendo HTTPSil proxy del traffico utilizzando a MITM Proxy like Burp Suite, Fiddler, OWASP ZAP, etc.The Proxies verrà restituito un certificato firmato da una CA autofirmata in modo che il proxy sia in grado di acquisire il HTTPStraffico.

Gli host di produzione non devono mai utilizzare certificati non attendibili per ovvi motivi .

Se è necessario che il simulatore iOS accetti un certificato non attendibile a scopo di test, si consiglia vivamente di non modificare la logica dell'applicazione per disabilitare la convalida del certificato integrata fornita dalle NSURLConnectionAPI. Se l'applicazione viene rilasciata al pubblico senza rimuovere questa logica, sarà suscettibile agli attacchi man-in-the-middle.

Il modo consigliato per accettare certificati non attendibili a scopo di test è importare il certificato dell'autorità di certificazione (CA) che ha firmato il certificato sul simulatore iOS o sul dispositivo iOS. Ho scritto un breve post sul blog che dimostra come fare ciò che un simulatore iOS su:

accettare certificati non attendibili utilizzando il simulatore ios


1
Roba fantastica, amico. Sono d'accordo, è così facile dimenticare di disabilitare questa logica app speciale per accettare qualsiasi certificato non attendibile.
Tomasz,

"Idealmente, ci dovrebbero essere solo due scenari in cui un'applicazione iOS dovrebbe accettare un certificato non attendibile." - Che ne dici di rifiutare un certificato "rivendicato" quando si blocca un certificato? Confer: Dignotar (pwn'd) e Trustwave (fama MitM).
jww

Completamente d'accordo con la tua dichiarazione sull'oblio di rimuovere il codice. L'ironia è che è molto più semplice apportare questa modifica al codice che far accettare al simulatore i certificati autofirmati.
devios1,

12

NSURLRequestha un metodo privato chiamato setAllowsAnyHTTPSCertificate:forHost:, che farà esattamente quello che ti piacerebbe. È possibile definire il allowsAnyHTTPSCertificateForHost:metodo su NSURLRequesttramite una categoria e impostarlo per la restituzione YESper l'host che si desidera sostituire.


Si applicano i soliti avvertimenti sulle API non documentate ... ma è bene sapere che è possibile.
Stephen Darlington,

Sì, assolutamente. Ho aggiunto un'altra risposta che non comporta l'uso di API private.
Nathan de Vries,

Funziona quando si utilizza "NSURLConnection sendSynchronousRequest:"?
Tim Büthe

11

A complemento della risposta accettata, per una sicurezza molto migliore, è possibile aggiungere il certificato del server o il proprio certificato CA radice al portachiavi ( https://stackoverflow.com/a/9941559/1432048 ), tuttavia farlo da soli non renderà NSURLConnection autenticare automaticamente il server autofirmato. Devi ancora aggiungere il seguente codice al tuo delegato NSURLConnection, è copiato dal codice di esempio Apple AdvancedURLConnections e devi aggiungere due file (Credentials.h, Credentials.m) dal codice di esempio Apple ai tuoi progetti.

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

Non posso prendermi il merito per questo, ma questo che ho trovato ha funzionato davvero bene per le mie esigenze. shouldAllowSelfSignedCertè la mia BOOLvariabile. Basta aggiungere al tuo NSURLConnectiondelegato e dovresti essere rockin per un bypass rapido in base alla connessione.

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

10

In iOS 9, le connessioni SSL falliranno per tutti i certificati non validi o autofirmati. Questo è il comportamento predefinito della nuova funzionalità di sicurezza del trasporto app in iOS 9.0 o versioni successive e su OS X 10.11 e versioni successive.

È possibile ignorare questo comportamento in Info.plist, impostando NSAllowsArbitraryLoadssu YESnel NSAppTransportSecuritydizionario. Tuttavia, raccomando di ignorare questa impostazione solo a scopo di test.

inserisci qui la descrizione dell'immagine

Per informazioni, consultare Technote di trasporto app qui .


L'unica soluzione ha funzionato per me, non ho modo di cambiare il framework Firebase per soddisfare le mie esigenze, che ha risolto, grazie!
Yitzchak,

Ora ho visto che Google chiede NSAllowArbitraryLoads = YES per Admob (in Firebase). firebase.google.com/docs/admob/ios/ios9
Yitzchak

6

La soluzione alternativa alla categoria pubblicata da Nathan de Vries supererà i controlli API privati ​​AppStore ed è utile nei casi in cui non si ha il controllo NSUrlConnectiondell'oggetto. Un esempio è NSXMLParserche aprirà l'URL fornito, ma non esporrà NSURLRequesto NSURLConnection.

In iOS 4 la soluzione sembra ancora funzionare, ma solo sul dispositivo, il simulatore non invoca più il allowsAnyHTTPSCertificateForHost:metodo.


6

Devi usare NSURLConnectionDelegateper consentire le connessioni HTTPS e ci sono nuovi callback con iOS8.

deprecato:

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

Invece quelli, è necessario dichiarare:

connectionShouldUseCredentialStorage: - Inviato per determinare se il caricatore URL deve utilizzare l'archivio credenziali per autenticare la connessione.

connection:willSendRequestForAuthenticationChallenge: - Indica al delegato che la connessione invierà una richiesta per una richiesta di autenticazione.

Con willSendRequestForAuthenticationChallengete puoi usare challengecome hai fatto con i metodi obsoleti, ad esempio:

// Trusting and not trusting connection to host: Self-signed certificate
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];

Potresti controllare questo: stackoverflow.com/q/56627757/1364053
nr5

3

Ho pubblicato un codice gist (basato sul lavoro di qualcun altro che noto) che ti consente di autenticarti correttamente rispetto a un certificato auto generato (e come ottenere un certificato gratuito - vedi i commenti in fondo a Cocoanetics )

Il mio codice è qui github


Potresti controllare questo: stackoverflow.com/q/56627757/1364053
nr5

2

Se vuoi continuare a usare sendSynchronousRequest, lavoro in questa soluzione:

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

puoi vederlo qui: Connessione sincrona SSL Objective-C


1

Con AFNetworking ho consumato con successo il servizio web https con il codice seguente,

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

1

Puoi usare questo codice

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

Utilizzare al -connection:willSendRequestForAuthenticationChallenge:posto di questi metodi obsoleti

deprecato:

-(BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace  
-(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge 
-(void)connection:(NSURLConnection *)connection didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.