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

iOS-以编程方式信任CA(证书颁发机构),ios,ssl,certificate,programmatically-created,Ios,Ssl,Certificate,Programmatically Created,我正在尝试使用一个应用程序,该应用程序通过套接字/流安全地从iPhone客户端连接到TLS服务器,用于一般数据交换。为此,我使用mackeychain工具建立了自己的CA,并将证书包含在代码包中 现在,我的应用程序应该信任该CA颁发的任何服务器证书。(我不在乎其他应用程序如何处理这些证书,我假设他们不会信任它,因为沙箱。) 我在网上发现了几个类似的问题,但似乎出了点问题 如果我将CA证书拖放到模拟器中并手动接受以信任它,那么与服务器的连接似乎工作正常 但是,当我尝试以编程方式为CA证书建立信任时

我正在尝试使用一个应用程序,该应用程序通过套接字/流安全地从iPhone客户端连接到TLS服务器,用于一般数据交换。为此,我使用mackeychain工具建立了自己的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;

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

err=SecTrustEvaluate(trust,&trustResult);
assert(err==noErr);
trustResult
是您感兴趣的内容,而不是从
SecTrustEvaluate
返回的
err
err
告诉您API调用是否成功/失败;它不会告诉您信任评估的结果

我认为你有两种策略。首先是在
trustResult
中查找值为
kSecTrustResultProceed
kSecTrustResultUnspecified
的“success”。它之所以“成功”,是因为它不“及时”,不“试图恢复”,也不“失败”

第二种策略是
trustResult
中的“不失败”,其值为
ksectrustsresultdeny
ksectrustsresultfailure
ksectrustsresulttothererror
。也就是说,只要
trustResult
不是这些值中的一个,那么就作为成功进行。忽略提示用户信任证书,因为他们无法理解提示并“点击”

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

我认为苹果的、苹果的技术笔记TN2232和苹果的技术问答QA1360可能对你有用



尽管做出了种种努力,但我仍然无法用NS/CF流复制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)

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


另外,我希望把以上作为一个答案不违反任何规则。。。这肯定不是正确/完整的答案,但开始一个全新的问题似乎没有用,不幸的是,文本太长,无法发表任何评论。如果有更好的方法在列出仍不清楚的点的同时包含其他信息(如上面的新链接),请告诉我。

我得到kSecResultUnspecified。由于系统似乎对此结果不满意,我是否需要完全禁用自动TLS验证,而不是手动检查SecTrustEvaluate,然后如果我得到kSecResultUnspecified,是否继续?因为我需要在服务器和客户端之间进行灵活的通信,所以我认为我需要使用NS/CF流而不是NSURLConnections。除了stream:handleEvent:我不知道NS/CF流有代理,是吗?有没有任何方法可以让我实现ksecresultproceduce而不必默认信任,也不必要求用户接受我的根CA?@MacMini-请参阅苹果的技术问答QA1360。或者查看上面源代码中的注释。当系统返回
kSecResultUnspecified
时,它不会不高兴。感谢您快速而详细的回答,代码肯定会很有价值!在你对我的评论的回复中,你说:系统与'kSecT'配合良好
- (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];
}