iOS - 以编程方式信任CA(证书颁发机构)

时间:2014-08-05 15:14:16

标签: ios ssl certificate programmatically-created

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

现在我的应用应该信任该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;

2 个答案:

答案 0 :(得分:3)

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

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

trustResult是您感兴趣的内容,而非err来自SecTrustEvaluate的回复。 err告诉您API调用是否成功/失败;它没有告诉你信任评估的结果。

我认为你有两种策略。首先是寻找成功"在trustResult中,其值为kSecTrustResultProceedkSecTrustResultUnspecified。它的成功"因为"提示",其"尝试恢复"及其"失败"。

第二个策略是"不是失败"在trustResult中,其值为kSecTrustResultDenykSecTrustResultFatalTrustFailurekSecTrustResultOtherError。也就是说,只要trustResult不是这些值中的一个,那就继续成功。忽略提示用户信任证书,因为他们不理解提示并且"点按"。

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

我认为Apple的Overriding TLS Chain Validation Correctly,Apple的Tech Note TN2232,HTTPS Server Trust Evaluation和Apple的技术Q& A QA1360,Describing the kSecTrustResultUnspecified error可能会有用给你。


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

答案 1 :(得分:1)

尽管所有的努力我都无法与NS / CF-Streams复制,但是显然用NSURLConnection管理了什么。 (与之前一样,在模拟器上拖放CA-cert并将其视为受信任时,TLS流工作正常,但以编程方式执行此操作不会成功。)

因此我对这个主题进行了一些搜索,实际上又找到了一个由Rob Napier回答的aeternusrahl的帖子:Post

...其中引用了Heath Borders的以下博客文章: Blog

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

没有复制这些链接的全部内容:Rob Napier说'......据我所知,在每个人共享的钥匙链中只有一个可靠的锚列表。这对socket.io没有多大帮助,因为它不允许您访问其NSURLConnection委托方法。您必须修改socket.io以接受信任锚。'

由于上述所有作者都有出色的声誉,而且我对iOS编程很陌生,所以我不敢不同意任何一个!但由于我很难巩固这些职位,所以我非常感谢任何有关我可能不小心跳过的职位的先决条件的澄清。

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

基本上我看到以下可能的解释: A)jww仅指NSURLConnections,其委托似乎比使用NS / CF流时遇到的更强大 B)自Rob Napier / Heath Borders博客发布以来,情况发生了变化 C)在这种情况下我完全错了,并提前道歉!

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

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

PS。我希望将上述内容作为答案并不违反任何规则......这肯定不是正确/完整的答案,但是开始一个全新的问题似乎既没有用,不幸的是,文本对于任何评论都太长了。如果有更好的方法来列出其他信息(如上面的新链接),同时列出仍不清楚的点,请告诉我。