Aplicación de iOS SSL .p12 Autenticación: error de certificate incorrecto (-9825)

Actualizaciones

Edición 2/6/14:

Creé un server local Apache Tomcat para probar SSL con authentication de certificates. ¡Tuve éxito! Todo funciona como se esperaba, usando mis dos enfoques a continuación. (MKNetworkKit y código personalizado). Si bien esto me dice que mi código está funcionando, mi problema original aún no se ha resuelto. Actualicé el título de la pregunta para reflejar más específicamente el problema. ¿Alguien sabe si SAP Portal necesita configuraciones especiales para aceptar certificates de una aplicación de iOS? Recuerde, pude autenticar con éxito usando Safari mobile después de importar el CA y .p12 en el llavero compartido, solo tuve un código infructuoso (que ahora sé que el código funciona, pero no con el portal).


Estoy creando una aplicación iOS7 Cordova 3.2 muy simple con un complemento personalizado para get datos de un service web SSL al proporcionar solo un certificate .p12 para la authentication (no se necesitan cnetworkingenciales básicas u otras cnetworkingenciales de usuario). Estoy realizando todas las testings en un iPad físico (sin simulador). El service web reside en un cuadro de desarrollo de portal SAP NetWeaver con un certificate autofirmado. Por ahora, he importado la CA del server en el llavero iOS para evitar errores de confianza de los certificates. Para propósitos de testing, mi certificate .p12 se incluye localmente dentro de la aplicación, en la raíz de mainBundle.

Cuando trato de conectarme al service web obtengo el siguiente error en la console:

CFNetwork SSLHandshake failed (-9825) NSURLConnection/CFURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9825) Error Domain=NSURLErrorDomain Code=-1205 "The server “myhostremoved.com” did not accept the certificate." UserInfo=0x14e99000 {NSErrorFailingURLStringKey=https://myhostremoved.com/sslwebservice/, NSErrorFailingURLKey=https://myhostremoved.com/sslwebservice/, NSLocalizedDescription=The server “myhostremoved.com” did not accept the certificate., NSUnderlyingError=0x14d9b1d0 "The server “myhostremoved.com” did not accept the certificate.", NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x14d94720>} 

De acuerdo con el sitio de documentation de Apple , el error -9825 se refiere a un certificate incorrecto.

Hay muchas preguntas sobre SO relacionadas con lo que bash hacer, pero ninguna relacionada específicamente con el error que estoy viendo. Me acerqué al desarrollo del código de dos maneras diferentes.

Primero traté de usar código ya en SO, adaptándolo a mi caso de uso. Vea el código a continuación:

 - (void)startConnection:(CDVInvokedUrlCommand*)command { NSDictionary *options = [command.arguments objectAtIndex:0]; NSURL *serverURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@", [options objectForKey:@"host"]]];//hostname provided by Cordova plugin, but could just as easily be hardcoded here NSMutableURLRequest *connectionRequest = [NSMutableURLRequest requestWithURL:serverURL]; NSURLConnection *connection = nil; connection = [[NSURLConnection alloc] initWithRequest:connectionRequest delegate:self startImmediately:YES]; } - (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { // gets a certificate from local resources NSString *thePath = [[NSBundle mainBundle] pathForResource:@"mycert" ofType:@"p12"]; NSData *PKCS12Data = [[NSData alloc] initWithContentsOfFile:thePath]; CFDataRef inPKCS12Data = (__bridge CFDataRef)PKCS12Data; SecIdentityRef identity; // extract the ideneity from the certificate [self extractIdentity :inPKCS12Data :&identity]; SecCertificateRef certificate = NULL; SecIdentityCopyCertificate (identity, &certificate); const void *certs[] = {certificate}; CFArrayRef certArray = CFArrayCreate(kCFAllocatorDefault, certs, 1, NULL); // create a cnetworkingential from the certificate and ideneity, then reply to the challenge with the cnetworkingential NSURLCnetworkingential *cnetworkingential = [NSURLCnetworkingential cnetworkingentialWithIdentity:identity certificates:(__bridge NSArray*)certArray persistence:NSURLCnetworkingentialPersistencePermanent]; [challenge.sender useCnetworkingential:cnetworkingential forAuthenticationChallenge:challenge]; } - (OSStatus)extractIdentity:(CFDataRef)inP12Data :(SecIdentityRef*)identity { OSStatus securityError = errSecSuccess; CFStringRef password = CFSTR("MyCertPassw0rd"); const void *keys[] = { kSecImportExportPassphrase }; const void *values[] = { password }; CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL); CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL); securityError = SecPKCS12Import(inP12Data, options, &items); if (securityError == 0) { CFDictionaryRef ident = CFArrayGetValueAtIndex(items,0); const void *tempIdentity = NULL; tempIdentity = CFDictionaryGetValue(ident, kSecImportItemIdentity); *identity = (SecIdentityRef)tempIdentity; } if (options) { CFRelease(options); } return securityError; } 

En mi segundo enfoque , traté de usar la biblioteca MKNetworkKit, que extrae mucho del código necesario para interactuar con el certificate. Todo lo que necesita hacer es proporcionar la ruta al certificate y la contraseña. Una vez más, obtengo el mismo error que el anterior. Este código está debajo.

 - (void)startConnection:(CDVInvokedUrlCommand*)command { NSDictionary *options = [command.arguments objectAtIndex:0]; NSURL *serverURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@", [options objectForKey:@"host"]]];//hostname provided by Cordova plugin, but could just as easily be hardcoded here MKNetworkEngine *engine = [[MKNetworkEngine alloc] initWithHostName:serverURL customHeaderFields:nil]; MKNetworkOperation *op = [engine operationWithPath:nil params:nil httpMethod:@"GET" ssl:YES]; NSString *thePath = [[NSBundle mainBundle] pathForResource:@"mycert" ofType:@"p12"]; [op setShouldContinueWithInvalidCertificate:YES]; op.clientCertificate = thePath; op.clientCertificatePassword = @"MyCertPassw0rd"; [op addCompletionHandler:^(MKNetworkOperation *operation) { NSLog(@"[operation responseData]-->>%@", [operation responseString]); }errorHandler:^(MKNetworkOperation *errorOp, NSError* err) { NSLog(@"MKNetwork request error : %@", [err localizedDescription]); }]; [engine enqueueOperation:op]; } 

Me sale el mismo error utilizando ambos enfoques. ¿Algunas ideas?

Información conocida

  • Sé que la aplicación está encontrando el certificate .p12 porque cuando path la ruta hacia el certificate .p12, recibo un error que dice que no puede encontrar el certificate que normalmente no veo.
  • Sé que la contraseña que estoy proporcionando para el file del certificate es correcta porque cuando la codifico, recibo un error con respecto a la contraseña que normalmente no veo de otra manera.
  • No creo que la confianza del certificate sea un problema porque si elimino la CA de mi server del llavero iOS, obtendré un error en la console indicando específicamente que no se puede confiar en el server. Después de volver a agregar la CA, este error ya no se produce, pero obtengo el mismo error que el anterior (-9825)

Otras testings

Al proporcionar authentication básica en código (omitir la authentication de certificates), todo funciona como se espera y no recibo errores.

También he intentado todos los mismos pasos anteriores, pero usando un file .pfx en lugar de mi certificate .p12, los mismos errores.

El service SSL funciona en Safari mobile. Importé el certificate .p12 en mi llavero a través de la utilidad de configuration de iPhone, y probé el service web a través de safari mobile. Todo funciona como se esperaba, no se producen errores (tampoco hay errores de confianza) y recibo el resultado esperado.

También puedo probar con éxito el service web en mi escritorio utilizando la utilidad rest-client

TL; DR

Estoy intentando autenticarme en un service web SSL utilizando solo un certificate .p12 en el objective c. Sé que el server y el service web funcionan con el certificate de authentication, pero sigo recibiendo errores cuando bash establecer la connection en el objective c, por lo que algo debe estar mal en mi código. ¡Por favor ayuda!

Finalmente pude resolver este problema. Encontré la respuesta aquí http://oso.com.pl/?p=207&lang=en

Parece que el certificate se envió dos veces, provocando un error en el server.

En caso de que el enlace anterior muera, encontré la misma solución ya en SO (pero para una pregunta y error diferente).

Al utilizar la authentication de certificate de cliente, ¿por qué sigo recibiendo NSURLErrorDomain Code = -1206?

En definitiva, cambiando esto:

  NSURLCnetworkingential *cnetworkingential = [NSURLCnetworkingential cnetworkingentialWithIdentity:myIdentity certificates:(__bridge NSArray*)certsArray persistence:NSURLCnetworkingentialPersistenceNone]; 

a esto:

  NSURLCnetworkingential *cnetworkingential = [NSURLCnetworkingential cnetworkingentialWithIdentity:myIdentity certificates:nil persistence:NSURLCnetworkingentialPersistenceNone]; 

resuelto mi problema Sería bueno si alguien pudiera ofrecer más información sobre este problema.

En algunos casos, el protocolo de enlace puede fallar porque el server también solicita certificates intermedios y de raíz.

Para solucionarlo, haga lo siguiente:

Pieza de desafío de authentication:

  OSStatus status = extractIdentityAndTrust(inP12data, &myIdentity, &myTrust, password); SecCertificateRef myCertificate; SecIdentityCopyCertificate(myIdentity, &myCertificate); CFIndex count = SecTrustGetCertificateCount(myTrust); NSMutableArray* myCertificates = [NSMutableArray arrayWithCapacity:count]; if (count > 1) { for (int i = 1; i < count; ++i) { [myCertificates addObject:(__bridge id)SecTrustGetCertificateAtIndex(myTrust, i)]; } } NSURLCnetworkingential *cnetworkingential = [NSURLCnetworkingential cnetworkingentialWithIdentity:myIdentity certificates:myCertificates persistence: NSURLCnetworkingentialPersistenceForSession]; 

Método de extracción

 OSStatus extractIdentityAndTrust(CFDataRef inPKCS12Data, SecIdentityRef *outIdentity, SecTrustRef *outTrust, CFStringRef keyPassword) { OSStatus securityError = errSecSuccess; const void *keys[] = { kSecImportExportPassphrase }; const void *values[] = { keyPassword }; CFDictionaryRef optionsDictionary = NULL; /* Create a dictionary containing the passphrase if one was specified. Otherwise, create an empty dictionary. */ optionsDictionary = CFDictionaryCreate( NULL, keys, values, (keyPassword ? 1 : 0), NULL, NULL); CFArrayRef items = NULL; securityError = SecPKCS12Import(inPKCS12Data, optionsDictionary, &items); if (securityError == 0) { CFDictionaryRef myIdentityAndTrust = CFArrayGetValueAtIndex (items, 0); const void *tempIdentity = NULL; tempIdentity = CFDictionaryGetValue (myIdentityAndTrust, kSecImportItemIdentity); CFRetain(tempIdentity); *outIdentity = (SecIdentityRef)tempIdentity; const void *tempTrust = NULL; tempTrust = CFDictionaryGetValue (myIdentityAndTrust, kSecImportItemTrust); CFRetain(tempTrust); *outTrust = (SecTrustRef)tempTrust; } if (optionsDictionary) CFRelease(optionsDictionary); if (items) CFRelease(items); return securityError; } 

Tenga en count que el ciclo de 1 (no 0) no es un error. El primer certificate ya está agregado a "myIdentify", por lo que solo otros certificates deben pasarse como certificates, de lo contrario, es probable que reciba un error durante el apretón de manos debido a la duplicación.