¿Cómo usar NSURLConnection para conectarse con SSL para un certificado no confiable?

300

Tengo el siguiente código simple para conectarme a una página web SSL

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

Excepto que da un error si el certificado es uno autofirmado. Error Domain=NSURLErrorDomain Code=-1202 UserInfo=0xd29930 "untrusted server certificate".¿Hay alguna forma de configurarlo para que acepte conexiones de todos modos (al igual que en un navegador puede presionar aceptar) o una forma de evitarlo?

erotsppa
fuente

Respuestas:

415

¡Hay una API compatible para lograr esto! Agregue algo como esto a su NSURLConnectiondelegado:

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

Tenga en cuenta que connection:didReceiveAuthenticationChallenge:puede enviar su mensaje a challenge.sender (mucho) más tarde, después de presentar un cuadro de diálogo al usuario si es necesario, etc.

Gordon Henriksen
fuente
31
Muchas gracias, funciona perfectamente. Simplemente elimine los dos ifs y conserve solo la parte useCendential en la devolución de llamada didReceiveAuthentificationChallenge si desea aceptar cualquier sitio https.
yonel
19
qué es un TrustedHosts, donde n cómo se define el objeto
Ameya
77
Ameya, sería un NSArray de objetos NSString. Las cadenas son los nombres de host como @ "google.com".
William Denniss
19
Este código funciona bien. Pero tenga en cuenta que el objetivo de tener certificados válidos es evitar ataques de hombre en el medio. Tenga en cuenta que si usa este código, alguien puede falsificar el llamado "host de confianza". Todavía obtiene las características de cifrado de datos de SSL, pero pierde las características de validación de identificación del host.
William Denniss
42
Estos métodos ahora se consideran obsoletos a partir de iOS 5.0 y Mac OS X 10.6. El -(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challengemétodo debe usarse en su lugar.
Andrew R.
36

Si no está dispuesto (o no puede) usar API privadas, hay una biblioteca de código abierto (licencia BSD) llamada ASIHTTPRequest que proporciona un contenedor alrededor del nivel inferior CFNetwork APIs. Recientemente introdujeron la capacidad de permitir el HTTPS connectionsuso de certificados autofirmados o no confiables con la -setValidatesSecureCertificate:API. Si no desea extraer toda la biblioteca, puede usar la fuente como referencia para implementar la misma funcionalidad usted mismo.

Nathan de Vries
fuente
2
Tim, es posible que desees usar asíncrono por otras razones de todos modos (como poder mostrar una barra de progreso), creo que para todas las solicitudes, excepto para las más simples, esa es mi forma de proceder. Entonces, tal vez debería implementar Async ahora y guardar la molestia más adelante.
William Denniss
Vea esto para la implementación (pero use [r setValidatesSecureCertificate: NO];): stackoverflow.com/questions/7657786/…
Sam Brodkin
Lamento haber traído este tema nuevamente. Pero desde que iOS 5 introdujo las características ARC. ¿Cómo puedo hacer que esto funcione ahora?
Melvin Lai
¿Podría comprobar esto: stackoverflow.com/q/56627757/1364053
nr5
33

Idealmente, solo debería haber dos escenarios en los que una aplicación iOS necesitaría aceptar un certificado no confiable.

Escenario A: está conectado a un entorno de prueba que utiliza un certificado autofirmado.

Escenario B: está enviando HTTPStráfico mediante un MITM Proxy like Burp Suite, Fiddler, OWASP ZAP, etc.proxy Los proxy devolverán un certificado firmado por una CA autofirmada para que el proxy pueda capturarHTTPS tráfico.

Los hosts de producción nunca deben usar certificados no confiables por razones obvias .

Si necesita que el simulador de iOS acepte un certificado no confiable para fines de prueba, se recomienda encarecidamente que no cambie la lógica de la aplicación para deshabilitar la validación de certificado incorporada proporcionada por el NSURLConnection API. Si la aplicación se lanza al público sin eliminar esta lógica, será susceptible a ataques de hombre en el medio.

La forma recomendada de aceptar certificados no confiables para fines de prueba es importar el certificado de la Autoridad de certificación (CA) que firmó el certificado en su simulador de iOS o dispositivo iOS. Escribí una publicación rápida en el blog que demuestra cómo hacer esto en un simulador de iOS en:

aceptar certificados no confiables utilizando el simulador ios

user890103
fuente
1
Cosas impresionantes hombre. Estoy de acuerdo, es muy fácil olvidarse de deshabilitar esta lógica de aplicación especial para aceptar cualquier certificado que no sea de confianza.
Tomasz
"Idealmente, solo debería haber dos situaciones en las que una aplicación iOS necesitaría aceptar un certificado no confiable". - ¿Qué tal rechazar un buen certificado 'reclamado' al fijar un certificado? Conferir: Dignotar (pwn'd) y Trustwave (fama MitM).
jww
Totalmente de acuerdo con su declaración sobre olvidarse de eliminar el código. La ironía es que es mucho más fácil hacer este cambio en el código que hacer que el simulador acepte certificados autofirmados.
devios1
12

NSURLRequesttiene un método privado llamado setAllowsAnyHTTPSCertificate:forHost:, que hará exactamente lo que quieras. Puede definir el allowsAnyHTTPSCertificateForHost:método a NSURLRequesttravés de una categoría y configurarlo para que regrese YESpara el host que desea anular.

Nathan de Vries
fuente
Se aplican advertencias habituales sobre las API no documentadas ... pero es bueno saber que es posible.
Stephen Darlington
Si absolutamente. He agregado otra respuesta que no implica el uso de API privadas.
Nathan de Vries
¿Funciona eso cuando usas "NSURLConnection sendSynchronousRequest:"?
Tim Büthe
11

Para complementar la respuesta aceptada, para una seguridad mucho mejor, puede agregar su certificado de servidor o su propio certificado de CA raíz al llavero ( https://stackoverflow.com/a/9941559/1432048 ), sin embargo, hacer esto solo no hará que NSURLConnection autentique su servidor autofirmado automáticamente. Aún necesita agregar el código siguiente a su delegado NSURLConnection, se copia del código de muestra de Apple AdvancedURLConnections , y debe agregar dos archivos (Credentials.h, Credentials.m) del código de muestra de Apple a sus proyectos.

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

No puedo tomar ningún crédito por esto, pero este que encontré funcionó muy bien para mis necesidades. shouldAllowSelfSignedCertes mi BOOLvariable Simplemente agregue a su NSURLConnectiondelegado y debería estar listo para un bypass rápido por conexión.

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

En iOS 9, las conexiones SSL fallarán para todos los certificados no válidos o autofirmados. Este es el comportamiento predeterminado de la nueva función Seguridad de transporte de aplicaciones en iOS 9.0 o posterior, y en OS X 10.11 y posterior.

Puede anular este comportamiento en el Info.plist, mediante el establecimiento NSAllowsArbitraryLoadsde YESen el NSAppTransportSecuritydiccionario. Sin embargo, recomiendo anular esta configuración solo con fines de prueba.

ingrese la descripción de la imagen aquí

Para obtener información, consulte la nota técnica de transporte de aplicaciones aquí .

johnnieb
fuente
La única solución funcionó para mí, no tengo forma de cambiar el marco de Firebase para satisfacer mis necesidades, eso lo resolvió, ¡gracias!
Itzjak
Ahora vi que Google pregunta por NSAllowArbitraryLoads = YES para Admob (en Firebase). firebase.google.com/docs/admob/ios/ios9
Yitzchak
6

La solución alternativa de categoría publicada por Nathan de Vries pasará las verificaciones de API privadas de AppStore, y es útil en casos en los que no tiene control del NSUrlConnectionobjeto. Un ejemplo es el NSXMLParserque abrirá la URL que proporcione, pero no expone el NSURLRequesto NSURLConnection.

En iOS 4, la solución aún parece funcionar, pero solo en el dispositivo, el simulador ya no invoca el allowsAnyHTTPSCertificateForHost:método.

Alex Suzuki
fuente
6

NSURLConnectionDelegateDebe usar para permitir conexiones HTTPS y hay nuevas devoluciones de llamada con iOS8.

Obsoleto:

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

En cambio, debes declarar:

connectionShouldUseCredentialStorage: - Enviado para determinar si el cargador de URL debe usar el almacenamiento de credenciales para autenticar la conexión.

connection:willSendRequestForAuthenticationChallenge: - Le dice al delegado que la conexión enviará una solicitud de desafío de autenticación.

Con willSendRequestForAuthenticationChallengeusted puede usar challengecomo lo hizo con los métodos obsoletos, por ejemplo:

// 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
fuente
¿Podría por favor verificar esto: stackoverflow.com/q/56627757/1364053
nr5
3

Publiqué un código esencial (basado en el trabajo de otra persona que noto) que le permite autenticarse adecuadamente contra un certificado autogenerado (y cómo obtener un certificado gratuito - vea los comentarios al final de Cocoanetics )

Mi código está aquí github

David H
fuente
¿Podría comprobar esto: stackoverflow.com/q/56627757/1364053
nr5
2

Si desea seguir usando sendSynchronousRequest, trabajo en esta solución:

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

Puedes verlo aquí: Objective-C SSL Synchronous Connection

jgorozco
fuente
1

Con AFNetworking , he consumido con éxito el servicio web https con el siguiente código,

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

Puedes usar este Código

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

Usar en -connection:willSendRequestForAuthenticationChallenge:lugar de estos métodos obsoletos

Obsoleto:

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