iOS 应用 SSL .p12 身份验证 - 证书错误错误 (-9825)
Posted
技术标签:
【中文标题】iOS 应用 SSL .p12 身份验证 - 证书错误错误 (-9825)【英文标题】:iOS app SSL .p12 Authentication - bad certificate error (-9825) 【发布时间】:2014-02-02 16:07:17 【问题描述】:更新
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's documentation site,-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 utility 在我的桌面上成功测试 Web 服务
TL;DR
我正在尝试仅使用 Objective-c 中的 .p12 证书对 SSL Web 服务进行身份验证。我知道服务器和 Web 服务都可以使用证书进行身份验证,但是当我尝试在 Objective-C 中建立连接时,我不断收到错误消息,所以我的代码中一定有问题。请帮忙!
【问题讨论】:
【参考方案1】:我终于能够解决这个问题。我在这里找到了答案 http://oso.com.pl/?p=207&lang=en
证书好像被发送了两次,导致服务器出错。
如果上述链接失效,我已经在 SO 上找到了相同的解决方案(但针对不同的问题和错误)。
When using Client Certificate Authentication, why do I keep getting NSURLErrorDomain Code=-1206?
最终,改变这一点:
NSURLCredential *credential = [NSURLCredential credentialWithIdentity:myIdentity certificates:(__bridge NSArray*)certsArray persistence:NSURLCredentialPersistenceNone];
到这里:
NSURLCredential *credential = [NSURLCredential credentialWithIdentity:myIdentity certificates:nil persistence:NSURLCredentialPersistenceNone];
解决了我的问题。如果有人能提供更多关于这个问题的见解,那就太好了。
【讨论】:
【参考方案2】:在某些情况下,握手可能会失败,因为服务器还要求中间证书和根证书。
要修复它,请执行以下操作:
身份验证挑战部分:
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”中,所以只有其他证书应该作为证书传递,否则很可能会因为重复而在握手期间收到错误。
【讨论】:
以上是关于iOS 应用 SSL .p12 身份验证 - 证书错误错误 (-9825)的主要内容,如果未能解决你的问题,请参考以下文章