iOS 和 SSL:无法验证自签名服务器证书

Posted

技术标签:

【中文标题】iOS 和 SSL:无法验证自签名服务器证书【英文标题】:iOS and SSL: Unable to validate self-signed server certificate 【发布时间】:2012-06-11 12:15:30 【问题描述】:

我对使用 SSL 通道使用 Web 服务还很陌生。经过相当好的搜索,我找到了一种使用 NSURLConnection 委托 API 执行 SSL/HTTPS 身份验证的方法。以下是执行实际身份验证的代码 sn-p:

- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge 
[self printLogToConsole:@"Authenticating...."];
[self printLogToConsole:[NSString stringWithFormat:@"\n%@\n", [challenge description]]];
NSLog(@"\n\nserverTrust: %@\n", [[challenge protectionSpace] serverTrust]);

/* Extract the server certificate for trust validation
 */
NSURLProtectionSpace *protectionSpace = [challenge protectionSpace];
assert(protectionSpace);
SecTrustRef trust = [protectionSpace serverTrust];    
assert(trust);
CFRetain(trust); // Make sure this thing stays around until we're done with it
NSURLCredential *credential = [NSURLCredential credentialForTrust:trust];


/* On ios 
 * we need to convert it to 'der' certificate. It can be done easily through Terminal as follows:
 * $ openssl x509 -in certificate.pem -outform der -out rootcert.der
 */
NSString *path = [[NSBundle mainBundle] pathForResource:@"rootcert" ofType:@"der"];
assert(path);
NSData *data = [NSData dataWithContentsOfFile:path];
assert(data);

/* Set up the array of certificates, we will authenticate against and create credentials */
SecCertificateRef rtCertificate = SecCertificateCreateWithData(NULL, CFBridgingRetain(data));
const void *array[1] =  rtCertificate ;
trustedCerts = CFArrayCreate(NULL, array, 1, &kCFTypeArrayCallBacks);
CFRelease(rtCertificate); // for completeness, really does not matter

/* Build up the trust anchor using our root cert */
int err;
SecTrustResultType trustResult = 0;
err = SecTrustSetAnchorCertificates(trust, trustedCerts);
if (err == noErr) 
    err = SecTrustEvaluate(trust, &trustResult);

CFRelease(trust); // OK, now we're done with it

[self printLogToConsole:[NSString stringWithFormat:@"trustResult: %d\n", trustResult]];

/* http://developer.apple.com/library/mac/#qa/qa1360/_index.html
 */
BOOL trusted = (err == noErr) && ((trustResult == kSecTrustResultProceed) || (trustResult == kSecTrustResultConfirm) || (trustResult == kSecTrustResultUnspecified));

// Return based on whether we decided to trust or not
if (trusted) 
    [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
    [self printLogToConsole:@"Success! Trust validation successful."];
 else 
    [self printLogToConsole:@"Failed! Trust evaluation failed for service root certificate.\n"];
    [[challenge sender] cancelAuthenticationChallenge:challenge];

但我收到以下错误:

2012-06-11 17:10:12.541 SecureLogin[3424:f803] Error during connection: Error Domain=NSURLErrorDomain Code=-1012 "The operation couldn’t be completed. (NSURLErrorDomain error -1012.)" UserInfo=0x682c790 NSErrorFailingURLKey=https://staging.esecure.url/authentication/signin/merchants, NSErrorFailingURLStringKey=https://staging.esecure.url/authentication/signin/merchants

我正在使用从服务器获得的相同证书并将其转换为“der”格式。我正在为 iOS 5.x 构建应用程序。 我不确定我是否错过了什么。让我知道你的建议。

谢谢。

编辑 在此处检查证书后,输出的外观如何:

如果有什么问题,请告诉我。

谢谢。

【问题讨论】:

不,我没有使用 ASIHTTPRequest API。我正在使用 NSURLConnection API,目前无法更改为其他第三方库。我正在使用来自使用 OpenSSL 创建的服务器的自签名证书。 Problem with NSURLConnection 的可能重复项 【参考方案1】:

我无法判断您的代码是否有效,因为我使用 RestKit 来使用 REST 接口,但是导致 NSURLErrorDomain Code=-1012 的最常见问题是自签名证书没有指向 subject alternative name 的扩展名网络服务如果地址。

要检查您的证书,请下载Portecle app,如果您需要查看 ssl 证书内部,这非常有用。运行它并从菜单中选择检查->检查证书并导航到您的证书。您将看到有关您的证书的基本信息,现在按检查按钮,然后按主题备用名称,并确保您的 Web 服务的正确 IP 地址在那里。如果没有,您需要使用此信息再次创建证书。

【讨论】:

谢谢@lawicko。我下载了 Portecle 应用程序并检查了证书。 如您所见,“扩展”按钮是灰色的,这意味着您的证书没有。这就是证书在我的情况下没有验证的直接原因。我所做的只是让负责 Web 服务的人创建新证书。不幸的是,我只知道他为此使用了 openssl,所以我无法提供更多帮助,但我认为值得进一步调查。 谢谢谢谢谢谢谢谢!!【参考方案2】:

我确实想出了如何解决这个问题。

我最终逐字节比较了客户端和服务器信任证书。虽然可能有另一种方法来解决自签名证书的此类问题,但对于此解决方案确实有效。 下面是我如何使用它们的 CFData 对象逐字节比较客户端和服务器证书(您也可以参考 Apple 提供的“AdvancedURLConnections”示例代码):

success = NO;
        pServerCert = SecTrustGetLeafCertificate(trust);
        if (clientCert != NULL) 
            CFDataRef       clientCertData;
            CFDataRef       serverCertData;

            clientCertData = SecCertificateCopyData(clientCert);
            serverCertData   = SecCertificateCopyData(pServerCert);

            assert(clientCertData != NULL);
            assert(serverCertData   != NULL);

            success = CFEqual(clientCertData, serverCertData);

            CFRelease(clientCertData);
            CFRelease(serverCertData);
        
        if (success) 
            [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
            [self printLogToConsole:@"Success! Trust validation successful."];
         else 
            [self printLogToConsole:@"Failed! Trust evaluation failed for service root certificate.\n"];
            [[challenge sender] cancelAuthenticationChallenge:challenge];
        

希望这对正在寻找类似问题的解决方案的人有所帮助,

谢谢。

【讨论】:

以上是关于iOS 和 SSL:无法验证自签名服务器证书的主要内容,如果未能解决你的问题,请参考以下文章

手动验证 SSL 自签名证书签名 - javascript [重复]

iOS WKWebView 自签名证书单向验证+双向验证

针对 MITM 验证 SSL 证书

如何允许用户在 iOS 中使用 AFNetworking 信任和固定自签名 SSL 证书

如何让 python 信任我服务器的 TLS 自签名证书:ssl.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] 证书验证失败

如何使用 Ionic Cordova 框架和自签名证书绕过 iOS 11 中的 SSL 检查