4

我正在尝试使用一个应用程序,该应用程序通过套接字/流从 iPhone 客户端安全地连接到 TLS 服务器以进行一般数据交换。为此,我使用 mac keychain-tool 设置了自己的 CA,并将证书包含在代码包中。

现在我的应用程序应该信任该 CA 颁发的任何服务器证书。(我不在乎其他应用程序如何处理这些证书,我假设他们不会因为沙箱而信任它。)

我在网上发现了几个类似的问题,但似乎有问题。

如果我将 CA 证书拖放到模拟器中并手动接受以信任它,那么与服务器的连接似乎可以正常工作。

但是,当我尝试以编程方式建立对 CA 证书的信任时,我稍后与服务器的连接尝试被拒绝,尽管下面的代码不会产生错误。

因此我一定把证书实施部分弄错了......有什么想法吗?

提前谢谢了!

NSString* certPath = [[NSBundle mainBundle] pathForResource:@"MyTestCA2" ofType:@"cer"]; //cer = CA certificate
NSData* certData = [NSData dataWithContentsOfFile:certPath];
SecCertificateRef cert;
if( [certData length] ) {
    cert = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData);
    if( cert != NULL ) {
        CFStringRef certSummary = SecCertificateCopySubjectSummary(cert);
        NSString* summaryString = [[NSString alloc] initWithString:(__bridge NSString*)certSummary];
        NSLog(@"CERT SUMMARY: %@", summaryString);
        certSummary = nil;
    } else {
        NSLog(@" *** ERROR *** trying to create the SSL certificate from data located at %@, but failed", certPath);
    }
}

OSStatus err = noErr;
CFTypeRef result;
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:
                      (__bridge id)kSecClassCertificate, kSecClass,
                      cert, kSecValueRef,
                      nil];
err = SecItemAdd((__bridge CFDictionaryRef)dict, &result);
if(err!=noErr) NSLog(@"error while importing");
if (err==errSecDuplicateItem) NSLog(@"Cert already installed");
NSLog(@":%i",(int)err);
assert(err==noErr||err==errSecDuplicateItem);   // accept no errors other than duplicate
err = noErr;
SecTrustRef trust;
err = SecTrustCreateWithCertificates(cert, SecPolicyCreateBasicX509() ,&trust);
assert(err==noErr);
err = noErr;
CFMutableArrayRef newAnchorArray = CFArrayCreateMutable(kCFAllocatorDefault,0,&kCFTypeArrayCallBacks);
CFArrayAppendValue(newAnchorArray,cert);
err = SecTrustSetAnchorCertificates(trust, newAnchorArray);
assert(err==noErr);
SecTrustResultType trustResult;
err=SecTrustEvaluate(trust,&trustResult);
assert(err==noErr);
cert=nil;
4

2 回答 2

3

我没有尝试运行部分代码,但我有一些我知道有效的代码(如下提供)。我用它来信任我的内部 CA。

err=SecTrustEvaluate(trust,&trustResult);
assert(err==noErr);

trustResult是您感兴趣的内容,而不是err来自SecTrustEvaluate. err告诉您 API 调用是否成功/失败;它不会告诉您信任评估的结果。

我认为你在这里有两种策略。trustResult首先是在值kSecTrustResultProceed或中寻找“成功” kSecTrustResultUnspecified。它的“成功”,因为它不是“提示”,它不是“试图恢复”,它不是“失败”。

第二种策略是“不失败”的trustResult价值kSecTrustResultDenykSecTrustResultFatalTrustFailurekSecTrustResultOtherError。也就是说,只要trustResult不是这些值之一,则作为成功进行。忽略提示用户信任证书,因为他们不会理解提示并“点击通过”。

下面是我在NSURLConnection委托中使用的代码-didReceiveAuthenticationChallenge:。它需要一个 ASN.1/DER 编码的证书(名为ca-cert.der)。它使用上述策略 1。如果您使用 中的代码#ifdef 0,那么它使用策略 2。

我认为 Apple 的Overriding TLS Chain Validation Correctly 、Apple 的技术说明 TN2232、HTTPS 服务器信任评估和 Apple 的技术问答 QA1360,描述 kSecTrustResultUnspecified 错误可能对您有用。


- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:
(NSURLAuthenticationChallenge *)challenge
{   
    SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust];
    return [[challenge sender] useCredential: [NSURLCredential credentialForTrust: serverTrust]
                  forAuthenticationChallenge: challenge];

    if ([[[challenge protectionSpace] authenticationMethod] isEqualToString: NSURLAuthenticationMethodServerTrust])
    {
        do
        {
            SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust];
            NSCAssert(serverTrust != nil, @"serverTrust is nil");
            if(nil == serverTrust)
                break; /* failed */

            NSData* caCert = [NSData dataWithContentsOfFile:@"ca-cert.der"];
            NSCAssert(caCert != nil, @"caCert is nil");
            if(nil == caCert)
                break; /* failed */

            SecCertificateRef caRef = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)caCert);
            NSCAssert(caRef != nil, @"caRef is nil");
            if(nil == caRef)
                break; /* failed */

            NSArray* caArray = [NSArray arrayWithObject:(__bridge id)(caRef)];
            NSCAssert(caArray != nil, @"caArray is nil");
            if(nil == caArray)
                break; /* failed */

            OSStatus status = SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)caArray);
            NSCAssert(errSecSuccess == status, @"SecTrustSetAnchorCertificates failed");
            if(!(errSecSuccess == status))
                break; /* failed */

            SecTrustResultType result = -1;
            status = SecTrustEvaluate(serverTrust, &result);
            if(!(errSecSuccess == status))
                break; /* failed */

            NSLog(@"Result: %d", result);

            /* https://developer.apple.com/library/ios/technotes/tn2232/_index.html */
            /* https://developer.apple.com/library/mac/qa/qa1360/_index.html */
            /* kSecTrustResultUnspecified and kSecTrustResultProceed are success */
            if(result != kSecTrustResultUnspecified && result != kSecTrustResultProceed)
                break; /* failed */

#if 0
            /* Treat kSecTrustResultConfirm and kSecTrustResultRecoverableTrustFailure as success */
            /*   since the user will likely tap-through to see the dancing bunnies */
            if(result == kSecTrustResultDeny || result == kSecTrustResultFatalTrustFailure || result == kSecTrustResultOtherError)
                break; /* failed to trust cert (good in this case) */
#endif

            // The only good exit point
            return [[challenge sender] useCredential: [NSURLCredential credentialForTrust: serverTrust]
                          forAuthenticationChallenge: challenge];

        } while(0);
    }

    // Bad dog
    return [[challenge sender] cancelAuthenticationChallenge: challenge];
}
于 2014-08-05T15:40:08.303 回答
1

尽管我付出了所有努力,但仍然无法使用 NS/CF-Streams 进行复制,但 jww 显然已经使用 NSURLConnection 进行了管理。(和以前一样,在模拟器上拖放 CA 证书并将其接受为受信任时,TLS 流工作正常,但以编程方式执行它不会成功。)

因此,我对该主题进行了更多搜索,实际上发现了 aeternusrahl 的另一个帖子,该帖子由 Rob Napier 回答:发布

...其中引用了 Heath Borders 的以下博客条目: 博客

对于和我有类似问题的每个人,我非常推荐这些链接——它们提供了很好的洞察力!

在不复制这些链接的全部内容的情况下:Rob Napier 说“……据我所知,该钥匙串中只有一个受信任的锚点列表,每个人都共享。这对 socket.io 没有多大帮助,因为它不能让你访问它的 NSURLConnection 委托方法。您必须修改 socket.io 以接受信任锚。

由于上述所有作者都享有盛誉,而且我对 iOS 编程还很陌生,我不敢不同意任何一个!但由于我很难整合这些帖子,我非常感谢对我可能不小心跳过的帖子中的先决条件做出任何澄清。

对我来说,jww 似乎没有考虑在使用自己的根证书时禁用自动 TLS 验证(请参阅他对我在他的第一篇文章中的评论的回答)。然后,Heath Borders/Rob Napier 似乎建议在 ssl 设置中禁用证书链验证对于套接字是必要的(如果我做对了,再次验证。)

基本上我看到了以下可能的解释:A)jww 仅指 NSURLConnections,它的代表似乎比你在使用 NS/CF 流时遇到的那个更强大 B)自从 Rob Napier/ Heath Borders 的博客 C)我完全错了,在这种情况下提前道歉!

虽然A)似乎更有可能,但我有点希望B)......

我将非常感谢您提供更多见解!

PS。我希望将上述内容作为答案不会违反任何规则......这当然不是正确/完整的答案,但开始一个全新的问题似乎没有用,不幸的是,文本太长,无法发表任何评论。如果有更好的方法在列出仍不清楚的点的同时包含附加信息(如上述新链接),请告诉我。

于 2014-08-07T14:22:56.303 回答