iOS应用程序SSL .p12身份validation – 证书错误错误(-9825)

更新

编辑2/6/14:

我创build了一个本地Apache Tomcat服务器来testing带有证书authentication的SSL。 我成功了! 一切都如预期般运作,使用我的两种方法。 (MKNetworkKit和自定义代码)。 虽然这告诉我,我的代码正在工作,我的原始问题仍然没有解决。 我更新了问题的标题,更具体地反映了这个问题。 有谁知道SAP Portal是否需要特殊设置才能接受来自iOS应用程序的证书? 请记住,在将CA和.p12导入共享钥匙串之后,我能够使用Safari移动设备成功进行身份validation,但在代码(我现在知道代码工作,而不是门户网站)中只是失败。


我正在创build一个带定制插件的非常简单的iOS7 Cordova 3.2应用程序,通过仅提供一个用于身份validation的.p12证书(不需要基本身份validation或其他用户凭据)来从SSL Web服务获取数据。 我正在一个物理iPad(没有模拟器)上执行所有testing。 Web服务使用自签名证书存在于开发SAP NetWeaver门户框中。 现在,我将服务器的CA导入到iOS密钥链中,以避免证书信任错误。 出于testing目的,我的.p12证书在应用程序本地捆绑在mainBundle的根目录中。

当试图连接到Web服务时,在控制台中出现跟随错误:

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

根据苹果公司的文档网站 ,-9825错误反映了一个错误的证书。

有很多关于我所做的事情的问题,但没有具体涉及我所看到的错误。 我用两种不同的方式来开发代码。

首先,我尝试使用已经存在的代码,并将其适用于我的用例。 见下面的代码:

 - (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 credential from the certificate and ideneity, then reply to the challenge with the credential NSURLCredential *credential = [NSURLCredential credentialWithIdentity:identity certificates:(__bridge NSArray*)certArray persistence:NSURLCredentialPersistencePermanent]; [challenge.sender useCredential:credential 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; } 

在我的第二种方法中 ,我尝试使用MKNetworkKit库,该库提取了许多与证书接口所需的代码。 所有你需要做的是提供证书和密码的path。 再一次,我得到了与上面相同的错误。 这个代码如下。

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

我使用这两种方法得到相同的错误。 有任何想法吗?

已知的信息

  • 我知道,该应用程序正在寻找.p12证书,因为当我fudgepath.p12证书,我收到一个错误,说它无法find我通常不会看到的证书,否则。
  • 我知道我提供的证书文件的密码是正确的,因为当我密码密码,我收到一个密码错误,我通常不会看到其他的。
  • 我不认为证书信任是一个问题,因为如果我从iOS钥匙串中删除我的服务器的CA,我在控制台中得到一个错误,具体说明服务器不能被信任。 joinCA后,这个错误不再发生,但是我得到了和上面相同的错误(-9825)

其他testing

在代码中提供基本身份validation时(绕过证书身份validation),一切都按预期工作,并且不会收到任何错误。

我也尝试了上述所有相同的步骤,但使用.pfx文件,而不是我的.p12证书,相同的错误。

SSL服务在移动版Safari中运行。 我通过iPhoneconfiguration实用程序将.p12证书导入到我的钥匙串中,并通过移动Safari浏览器testing了Web服务。 一切按预期工作,没有发生错误(也没有信任错误),我收到预期的输出。

我也能够使用rest-client实用程序成功testing桌面上的Web服务

TL; DR

我正尝试使用objective-c中的.p12证书对SSL Web服务进行身份validation。 我知道服务器和Web服务都使用auth的证书,但是当我尝试在objective-c中build立连接时,我不断收到错误,所以在我的代码中一定是错误的。 请帮忙!

我终于能够解决这个问题。 我在这里find了答案http://oso.com.pl/?p=207&lang=en

它看起来像是两次发送证书,导致服务器上的错误。

成为上述链接死亡,我find了同样的解决scheme已经在SO(但不同的问题和错误)。

当使用客户端证书身份validation时,为什么我一直得到NSURLErrorDomain代码= -1206?

最终,改变这一点:

  NSURLCredential *credential = [NSURLCredential credentialWithIdentity:myIdentity certificates:(__bridge NSArray*)certsArray persistence:NSURLCredentialPersistenceNone]; 

对此:

  NSURLCredential *credential = [NSURLCredential credentialWithIdentity:myIdentity certificates:nil persistence:NSURLCredentialPersistenceNone]; 

解决了我的问题。 如果有人能够提供更多关于这个问题的见解,这将是很好的。

在某些情况下,握手可能会失败,因为服务器也会询问中间和根证书。

要解决它,请执行以下操作:

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)]; } } NSURLCredential *credential = [NSURLCredential credentialWithIdentity:myIdentity certificates:myCertificates persistence: NSURLCredentialPersistenceForSession]; 

提取方法

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

请注意,从1(而不是0)循环不是一个错误。 第一个证书已经添加到“myIdentify”,所以只有其他证书应该作为证书传递,否则很可能在握手期间由于重复而收到错误。