更新
2/6/14 编辑:
我创建了一个本地 Apache Tomcat 服务器来测试带证书身份验证的 SSL。我成功了!使用下面的两种方法,一切都按预期工作。(MKNetworkKit 和自定义代码)。虽然这确实告诉我我的代码正在运行,但我的原始问题仍未解决。我更新了问题的标题以更具体地反映问题。有谁知道 SAP Portal 是否需要特殊设置才能接受来自 iOS 应用程序的证书?请记住,在将 CA 和 .p12 导入共享钥匙串后,我能够使用 Safari 移动设备成功进行身份验证,我只是在代码中不成功(我现在知道代码有效,只是在门户网站上无效)。
我正在使用自定义插件创建一个非常简单的 iOS7 Cordova 3.2 应用程序,通过仅提供 .p12 证书进行身份验证(不需要基本身份验证或其他用户凭据)来从 SSL Web 服务获取数据。我在物理 iPad(无模拟器)上执行所有测试。Web 服务位于使用自签名证书的开发 SAP NetWeaver 门户框上。现在,我将服务器的 CA 导入 iOS 钥匙串以避免证书信任错误。出于测试目的,我的 .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>}
根据Apple 的文档站点,-9825 错误指的是错误的证书。
关于我想要做的事情有很多关于 SO 的问题,但没有一个具体与我看到的错误有关。我以两种不同的方式处理代码的开发。
首先,我尝试使用 SO 上已经存在的代码,使其适应我的用例。请参见下面的代码:
- (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 库,它抽象出许多与证书交互所需的代码。您需要做的就是提供证书的路径和密码。同样,我得到与上述相同的错误。此代码如下。
- (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 证书,因为当我伪造 .p12 证书的路径时,我收到一条错误消息,指出它找不到我通常看不到的证书。
- 我知道我为证书文件提供的密码是正确的,因为当我伪造密码时,我会收到一个关于我通常不会看到的密码的错误。
- 我不认为证书信任是一个问题,因为如果我从 iOS 钥匙串中删除我的服务器的 CA,我会在控制台中收到一个错误,特别指出服务器不能被信任。重新添加 CA 后,此错误不再发生,但我收到与上述相同的错误 (-9825)
其他测试
在代码中提供基本身份验证(绕过证书身份验证)时,一切都按预期工作,我没有收到任何错误。
我也尝试了上述所有相同的步骤,但使用 .pfx 文件而不是我的 .p12 证书,同样的错误。
SSL 服务在移动 Safari 中运行。我通过 iPhone 配置实用程序将 .p12 证书导入我的钥匙串,并通过移动 safari 测试了 Web 服务。一切都按预期工作,没有发生错误(也没有信任错误),并且我收到了预期的输出。
我还能够使用rest-client 实用程序在我的桌面上成功测试 Web 服务
TL;博士
我正在尝试仅使用 Objective-c 中的 .p12 证书对 SSL Web 服务进行身份验证。我知道服务器和 Web 服务都可以使用证书进行身份验证,但是当我尝试在 Objective-C 中建立连接时,我不断收到错误消息,所以我的代码中一定有问题。请帮忙!