Xcode SSL pinning 信任锚证书

Posted

技术标签:

【中文标题】Xcode SSL pinning 信任锚证书【英文标题】:Xcode SSL pinning trust anchor certificates 【发布时间】:2017-04-25 17:54:19 【问题描述】:

我不是 ios 和 SSL 固定专家。尝试将本地证书添加到锚点中以信任它们。

尝试了几个代码,但总是返回 kSecTrustResultRecoverableTrustFailure。

这段代码有什么问题? 我应该将 cer 转换为 der 吗? 我应该删除服务器证书并仅使用本地受信任的证书吗?

任何想法,输入?

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge 

NSString * derCaPath;
NSMutableArray * chain = [NSMutableArray array];

for(int i=0; i<= 3; i++)

    if (i==0)
        derCaPath = [[NSBundle mainBundle] pathForResource:@"dstrootcax3" ofType:@"cer"];
    if (i==1)
        derCaPath = [[NSBundle mainBundle] pathForResource:@"comodorsacertificationauthority" ofType:@"cer"];
    if (i==2)
        derCaPath = [[NSBundle mainBundle] pathForResource:@"geotrustglobalca" ofType:@"cer"];
    else
        derCaPath = [[NSBundle mainBundle] pathForResource:@"thawteprimaryrootca" ofType:@"cer"];

    NSData *derCA = [NSData dataWithContentsOfFile:derCaPath];

    SecCertificateRef caRef = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)derCA);

    //NSArray * chain = [NSArray arrayWithObject:(__bridge id)(caRef)];

    [chain addObject:(__bridge id)(caRef)];



caChainArrayRef = CFBridgingRetain(chain);

if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])

    SecTrustRef trust = nil;
    SecTrustResultType result;
    OSStatus err = errSecSuccess;

#if DEBUG
    
        NSLog(@"Chain received from the server (working 'up'):");
        CFIndex certificateCount = SecTrustGetCertificateCount(challenge.protectionSpace.serverTrust);
        for(int i = 0; i < certificateCount; i++) 
            SecCertificateRef certRef = SecTrustGetCertificateAtIndex(challenge.protectionSpace.serverTrust, i);
            //CFStringRef str = SecCertificateCopyLongDescription(kCFAllocatorDefault, certRef, nil);
            //NSLog(@"   %02i: %@", 1+i, str);
            //CFRelease(str);
        

        NSLog(@"Local Roots we trust:");
        for(int i = 0; i < CFArrayGetCount(caChainArrayRef); i++) 
            SecCertificateRef certRef = (SecCertificateRef) CFArrayGetValueAtIndex(caChainArrayRef, i);
            //CFStringRef str = SecCertificateCopyLongDescription(kCFAllocatorDefault, certRef, nil);
            //NSLog(@"   %02i: %@", 1+i, str);
            //CFRelease(str);
        
    
#endif

    if (checkHostname) 
        // We use the standard Policy of SSL - which also checks hostnames.
        // -- see SecPolicyCreateSSL() for details.
        //
        trust = challenge.protectionSpace.serverTrust;
        //
#if DEBUG
        NSLog(@"The certificate is expected to match '%@' as the hostname",
              challenge.protectionSpace.host);
#endif
     else 
        // Create a new Policy - which goes easy on the hostname.
        //

        // Extract the chain of certificates provided by the server.
        //
        CFIndex certificateCount = SecTrustGetCertificateCount(challenge.protectionSpace.serverTrust);
        NSMutableArray * chain = [NSMutableArray array];

        for(int i = 0; i < certificateCount; i++) 
            SecCertificateRef certRef = SecTrustGetCertificateAtIndex(challenge.protectionSpace.serverTrust, i);
            [chain addObject:(__bridge id)(certRef)];
        

        for(int i = 0; i < CFArrayGetCount(caChainArrayRef); i++) 
            SecCertificateRef certRef = (SecCertificateRef) CFArrayGetValueAtIndex(caChainArrayRef, i);
            [chain addObject:(__bridge id)(certRef)];
        


        // And create a bland policy which only checks signature paths.
        //
        if (err == errSecSuccess)
            err = SecTrustCreateWithCertificates((__bridge CFArrayRef)(chain),
                                                 SecPolicyCreateBasicX509(), &trust);
#if DEBUG
        NSLog(@"The certificate is NOT expected to match the hostname '%@' ",
              challenge.protectionSpace.host);
#endif
    ;

    // Explicity specify the list of certificates we actually trust (i.e. those I have hardcoded
    // in the app - rather than those provided by some randon server on the internet).
    //
    if (err == errSecSuccess)
        err = SecTrustSetAnchorCertificates(trust,caChainArrayRef);

    // And only use above - i.e. do not check the system its global keychain or something
    // else the user may have fiddled with.
    //
    if (err == errSecSuccess)
        err = SecTrustSetAnchorCertificatesOnly(trust, YES);

    if (err == errSecSuccess)
        err = SecTrustEvaluate(trust, &result);


    if (err == errSecSuccess) 
        switch (result) 
            case kSecTrustResultProceed:
                // User gave explicit permission to trust this specific
                // root at some point (in the past).
                //
                NSLog(@"GOOD. kSecTrustResultProceed - the user explicitly trusts this CA");
                [challenge.sender useCredential:[NSURLCredential credentialForTrust:trust]
                     forAuthenticationChallenge:challenge];
                goto done;
                break;
            case kSecTrustResultUnspecified:
                // The chain is technically valid and matches up to the root
                // we provided. The user has not had any say in this though,
                // hence it is not a kSecTrustResultProceed.
                //
                NSLog(@"GOOD. kSecTrustResultUnspecified - So things are technically trusted. But the user was not involved.");
                [challenge.sender useCredential:[NSURLCredential credentialForTrust:trust]
                     forAuthenticationChallenge:challenge];
                goto done;
                break;
            case kSecTrustResultInvalid:
                NSLog(@"FAIL. kSecTrustResultInvalid");
                break;
            case kSecTrustResultDeny:
                NSLog(@"FAIL. kSecTrustResultDeny (i.e. user said no explicitly)");
                break;
            case kSecTrustResultFatalTrustFailure:
                NSLog(@"FAIL. kSecTrustResultFatalTrustFailure");
                break;
            case kSecTrustResultOtherError:
                NSLog(@"FAIL. kSecTrustResultOtherError");
                break;
            case kSecTrustResultRecoverableTrustFailure:
                NSLog(@"FAIL. kSecTrustResultRecoverableTrustFailure (i.e. user could say OK, but has not been asked this)");
                // DM 25.04.2017 we allow connection for the moment
                [challenge.sender useCredential:[NSURLCredential credentialForTrust:trust]
                     forAuthenticationChallenge:challenge];
                goto done;
                break;
            default:
                NSAssert(NO,@"Unexpected result: %d", result);
                break;
        
        // Reject.
        [challenge.sender cancelAuthenticationChallenge:challenge];
        goto done;
    ;
    //CFStringRef str =SecCopyErrorMessageString(err,NULL);
    //NSLog(@"Internal failure to validate: result %@", str);
    //CFRelease(str);

    [[challenge sender] cancelAuthenticationChallenge:challenge];

done:
    if (!checkHostname)
        CFRelease(trust);
    return;

// In this example we can cancel at this point - as we only do
// canAuthenticateAgainstProtectionSpace against ServerTrust.
//
// But in other situations a more gentle continue may be appropriate.
//
// [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];

NSLog(@"Not something we can handle - so we're canceling it.");
[challenge.sender cancelAuthenticationChallenge:challenge];

【问题讨论】:

【参考方案1】:

您不应该自己执行此操作:API(如您所见)非常棘手,几乎不可能正确验证,除非您同时是 SSL 和 iOS 专家。

例如,kSecTrustResultRecoverableTrustFailure 可能意味着很多东西,例如过期的证书;您不希望您的应用程序允许这样做。与主机名验证相同:它永远不应该被禁用(即使是调试:很容易生成具有正确主机名的测试证书)。

我开发了一个用于 SSL pinning 的库,它负责处理所有这些事情: https://github.com/datatheorem/TrustKit 。您应该尝试一下,因为它是专门为您的用例设计的。

【讨论】:

Nabla,非常感谢您的回答和您项目的链接。我看了看它,它对我的​​项目有很大的发展。我认为最简单的方法是将我的 cer 证书转换为 der 并比较服务器和本地字节以信任连接。

以上是关于Xcode SSL pinning 信任锚证书的主要内容,如果未能解决你的问题,请参考以下文章

详解SSL证书之证书链

调试 javax.net.ssl.SSLHandshakeException:java.security.cert.CertPathValidatorException:找不到证书路径的信任锚

Xamarin Android 问题通过 HTTPS 连接到具有自签名证书的站点:“未找到证书路径的信任锚。”

ios 如何去掉 ssl pinning

未找到 Android SSL 连接的信任锚

Android自签名证书:找不到证书路径的信任锚