5

更新

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 中建立连接时,我不断收到错误消息,所以我的代码中一定有问题。请帮忙!

4

2 回答 2

4

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

看起来证书被发送了两次,导致服务器出错。

如果上述链接失效,我已经在 SO 上找到了相同的解决方案(但针对不同的问题和错误)。

使用客户端证书身份验证时,为什么我不断收到 NSURLErrorDomain Code=-1206?

最终,改变这一点:

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

对此:

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

解决了我的问题。如果有人能对这个问题提供更多的见解,那就太好了。

于 2014-02-10T15:20:35.130 回答
0

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

要修复它,请执行以下操作:

身份验证挑战部分:

    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”中,因此只有其他证书应作为证书传递,否则很可能由于重复而在握手期间收到错误。

于 2016-08-04T12:08:11.600 回答