Xcode的SSL寄托信任锚证书

问题描述:

我不是iOS和SSL钉扎专家。尝试将本地证书添加到锚点以信任它们。Xcode的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]; 
} 

你不应该这样做你自己:这些API(因为你可以看到)是非常棘手的,这是几乎不可能得到验证正确的,除非你既是SSL和iOS专家。

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

我曾在一个SSL固定库上工作,负责所有这些事情: https://github.com/datatheorem/TrustKit。您应该尝试一下,因为它是专门为您的用例设计的。

+0

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