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 信任锚证书的主要内容,如果未能解决你的问题,请参考以下文章
调试 javax.net.ssl.SSLHandshakeException:java.security.cert.CertPathValidatorException:找不到证书路径的信任锚