这是我使用自签名证书进行身份验证的非常标准的NSURLConnection回调:
- - (SecCertificateRef)certRefFromDerNamed:(NSString*)derFileName resultingDataRef:(CFDataRef*)dataRefPtr{
- NSString *thePath = [[NSBundle mainBundle] pathForResource:derFileName ofType:@"der"];
- NSData *certData = [[NSData alloc] initWithContentsOfFile:thePath];
- CFDataRef certDataRef = (__bridge_retained CFDataRef)certData;
- SecCertificateRef cert = SecCertificateCreateWithData(NULL,certDataRef);
- *dataRefPtr = certDataRef;
- return cert;
- }
- - (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
- if (connection == self.connection) {
- BOOL trusted = NO;
- if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
- SecPolicyRef policyRef = SecPolicyCreateBasicX509();
- SecCertificateRef cert1;
- CFDataRef certData1;
- cert1 = [self certRefFromDerNamed:@"some3rdpartycacert" resultingDataRef:&certData1];
- SecCertificateRef certArray[1] = { cert1 };
- CFArrayRef certArrayRef = CFArrayCreate(NULL,(void *)certArray,1,NULL);
- SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
- SecTrustSetAnchorCertificates(serverTrust,certArrayRef);
- SecTrustResultType trustResult;
- SecTrustEvaluate(serverTrust,&trustResult);
- trusted = (trustResult == kSecTrustResultUnspecified);
- CFRelease(certArrayRef);
- CFRelease(policyRef);
- CFRelease(cert1);
- CFRelease(certData1);
- }
- if (trusted) {
- [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
- } else {
- [challenge.sender performDefaultHandlingForAuthenticationChallenge:challenge];
- }
- }
- }
而且trustResult总是kSecTrustResultRecoverableTrustFailure.
证书本身有点问题.根据服务器上的curl cert主题名称与我正在连接的url不匹配.我已经联系了第三方公司,他们告诉我,我需要在我的代码中接受这个url不匹配.问题是我不知道如何在iOS上这样做.我可以完全绕过证书检查(通过简单地假设trusted = YES并调用useCredential)或完全失败.从安全角度来看,第一种解决方案显然是错误的,并且容易发生MITM攻击.
这是CURL输出(我在这里使用了相同证书的PEM版本):
- ukaszs-iMac:Preferences lukasz$ curl --verbose --cacert ~/Desktop/some3rdpartycacert.txt https://dev-service.some3rdparty.com:50101/
- * About to connect() to dev-service.some3rdparty.com port 50101 (#0)
- * Trying XXX.XXX.XXX.XXX...
- * connected
- * Connected to dev-service.some3rdparty.com (XXX.XXX.XXX.XXX) port 50101 (#0)
- * successfully set certificate verify locations:
- * CAfile: /Users/lukasz/Desktop/some3rdpartycacert.txt
- CApath: none
- * SSLv3,TLS handshake,Client hello (1):
- * SSLv3,Server hello (2):
- * SSLv3,CERT (11):
- * SSLv3,Request CERT (13):
- * SSLv3,Server finished (14):
- * SSLv3,Client key exchange (16):
- * SSLv3,TLS change cipher,Finished (20):
- * SSLv3,Finished (20):
- * SSL connection using AES256-SHA
- * Server certificate:
- * subject: C=CA; ST=Ontario; O=Some 3rdParty Corporation; CN=otherpage.some3rdparty.com; emailAddress=noc@some3rdparty.com
- * start date: 2013-10-30 16:52:14 GMT
- * expire date: 2013-10-30 16:52:14 GMT
- * SSL: certificate subject name 'otherpage.some3rdparty.com' does not match target host name 'dev-service.some3rdparty.com'
- * Closing connection #0
- * SSLv3,TLS alert,Client hello (1):
- curl: (51) SSL: certificate subject name 'otherpage.some3rdparty.com' does not match target host name 'dev-service.some3rdparty.com'
那么,如何忽略iOS上的这个特殊错误?
解决方法
您需要使用实际主机名创建特殊策略,然后从中创建和评估serverTrust.大致:
- SecPolicyRef policyRef = SecPolicyCreateSSL(true,CFSTR("otherpage.some3rdparty.com"));
- OSStatus status;
- SecTrustRef serverTrust;
- status = SecTrustCreateWithCertificates(certificatesFromOriginalServerTrust,policyRef,& serverTrust);
- // noErr == status?
- status = SecTrustSetAnchorCertificates(serverTrust,certArrayRef);
- // noErr == status?
- SecTrustResultType trustResult;
- status = SecTrustEvaluate(serverTrust,&trustResult);
- // noErr == status?
- if(kSecTrustResultProceed == trustResult || kSecTrustResultUnspecified == trustResult) {
- // all good
- }
附:您没有使用您创建的政策.
我刚刚找到了一个更完整的解释here.