如何使用NSURLConnection连接不受信任的证书的SSL?

307

我有以下简单的代码来连接到一个 SSL 网页。

NSMutableURLRequest *urlRequest=[NSMutableURLRequest requestWithURL:url];
[ NSURLConnection sendSynchronousRequest: urlRequest returningResponse: nil error: &error ];

除非证书是自签名的,否则会产生错误:Error Domain=NSURLErrorDomain Code=-1202 UserInfo=0xd29930 "untrusted server certificate". 有没有办法设置它来接受连接(就像在浏览器中可以按接受一样),或者有什么方法可以绕过这个问题?

13个回答

419

有支持这样做的API!将以下内容添加到您的NSURLConnection委托中:

- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
  return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
  if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
    if ([trustedHosts containsObject:challenge.protectionSpace.host])
      [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];

  [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
请注意,connection:didReceiveAuthenticationChallenge: 可以在必要时向用户呈现对话框之后再将其消息发送到 challenge.sender(晚得多)。

31
非常感谢,它完美地运行了。如果您想接受任何https网站,请只保留didReceiveAuthentificationChallenge回调中的useCredential部分,去掉两个if语句即可。 - yonel
19
TrustedHosts是什么,该对象在哪里以及如何定义? - Ameya
7
Ameya,这将是一个NSString对象的NSArray。这些字符串是主机名,例如@"google.com"。 - William Denniss
19
这段代码运行良好。但请注意,拥有有效证书的整个目的是为了防止中间人攻击。因此,如果您使用此代码,请注意有人可能会伪造所谓的“受信任主机”。您仍然可以获得 SSL 的数据加密功能,但失去了主机身份验证功能。 - William Denniss
42
自从iOS 5.0和Mac OS X 10.6之后,这些方法被视为弃用。应该使用-(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge方法来替代。 - Andrew R.
显示剩余23条评论

36
如果您不愿意(或无法)使用私有API,则有一个开源(BSD许可证)库称为ASIHTTPRequest,它提供了对低级别CFNetwork APIs的包装。他们最近引入了使用-setValidatesSecureCertificate: API允许使用自签名或不受信任的证书进行HTTPS连接的功能。如果您不想拉入整个库,可以使用源代码作为实现相同功能的参考。

2
Tim,你可能在其他方面想使用异步(例如能够显示进度条),我发现对于除了最简单的请求之外的所有请求,我都会采用这种方式。所以也许你现在应该实现异步并节省以后的麻烦。 - William Denniss
请参考以下实现方式(但使用[r setValidatesSecureCertificate:NO];):https://dev59.com/x1vUa4cB1Zd3GeqPyOyE - Sam Brodkin
很抱歉我又提起了这个话题。但是自从iOS 5引入了ARC功能,现在我该如何使它正常工作呢? - Melvin Lai
请您帮忙查看一下这个链接:https://dev59.com/bLTma4cB1Zd3GeqP8Io9 - nr5

33
理想情况下,iOS应用程序只需要接受两种未经信任的证书:

场景A:您连接到使用自签名证书的测试环境。

场景B:您正在使用MITM代理(如Burp Suite、Fiddler、OWASP ZAP等)代理HTTPS流量。代理将返回由自签名CA签名的证书,以便代理能够捕获HTTPS流量。

出于显而易见的原因,生产主机永远不应使用未经信任的证书。

如果您需要iOS模拟器接受未经信任的证书进行测试,则强烈建议您不要更改应用程序逻辑以禁用NSURLConnection API提供的内置证书验证。如果在删除此逻辑之前向公众发布应用程序,则该应用程序将容易受到中间人攻击。

接受未经信任的证书进行测试的推荐方法是将签署证书的证书颁发机构(CA)证书导入到iOS模拟器或iOS设备中。我写了一篇快速博客文章,演示了如何在iOS模拟器上做到这一点:

使用iOS模拟器接受不受信任的证书


1
太棒了,我同意,很容易忘记禁用这个特殊应用逻辑以接受任何不受信任的证书。 - Tomasz
理想情况下,iOS应用程序只需要接受两种未经信任的证书。那么,在固定证书时拒绝“声称”的好证书呢?可以参考Dignotar(被攻破)和Trustwave(中间人攻击声名狼藉)的情况。 - jww
完全同意您关于忘记删除代码的说法。讽刺的是,在代码中进行此更改比让模拟器接受自签名证书要容易得多。 - devios1

12

为了增强安全性,除了接受的答案外,您可以将服务器证书或自己的根CA证书添加到钥匙串中 (https://dev59.com/C2435IYBdhLWcg3wlRQf#9941559),但仅这样做不会使NSURLConnection自动验证您的自签名服务器。 您仍然需要将以下代码添加到您的NSURLConnection委托中,它是从苹果示例代码AdvancedURLConnections中复制的,并且需要将两个文件(Credentials.h、Credentials.m)从苹果示例代码添加到您的项目中。

- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
//        if ([trustedHosts containsObject:challenge.protectionSpace.host])

    OSStatus                err;
    NSURLProtectionSpace *  protectionSpace;
    SecTrustRef             trust;
    SecTrustResultType      trustResult;
    BOOL                    trusted;

    protectionSpace = [challenge protectionSpace];
    assert(protectionSpace != nil);

    trust = [protectionSpace serverTrust];
    assert(trust != NULL);
    err = SecTrustEvaluate(trust, &trustResult);
    trusted = (err == noErr) && ((trustResult == kSecTrustResultProceed) || (trustResult == kSecTrustResultUnspecified));

    // If that fails, apply our certificates as anchors and see if that helps.
    //
    // It's perfectly acceptable to apply all of our certificates to the SecTrust
    // object, and let the SecTrust object sort out the mess.  Of course, this assumes
    // that the user trusts all certificates equally in all situations, which is implicit
    // in our user interface; you could provide a more sophisticated user interface
    // to allow the user to trust certain certificates for certain sites and so on).

    if ( ! trusted ) {
        err = SecTrustSetAnchorCertificates(trust, (CFArrayRef) [Credentials sharedCredentials].certificates);
        if (err == noErr) {
            err = SecTrustEvaluate(trust, &trustResult);
        }
        trusted = (err == noErr) && ((trustResult == kSecTrustResultProceed) || (trustResult == kSecTrustResultUnspecified));
    }
    if(trusted)
        [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
}

[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}

12

NSURLRequest有一个名为setAllowsAnyHTTPSCertificate:forHost:的私有方法,该方法将正好执行您想要的操作。您可以通过分类在NSURLRequest上定义allowsAnyHTTPSCertificateForHost:方法,并将其设置为针对您要覆盖的主机返回YES


通常适用于未记录的API的警告适用...但知道这是可能的很好。 - Stephen Darlington
是的,绝对没问题。我添加了另一个答案,它不涉及使用私有 API。 - Nathan de Vries
当您使用“NSURLConnection sendSynchronousRequest:”时,是否有效? - Tim Büthe

11

我对此无法负责,但是我找到的这个方法非常适合我的需求。 shouldAllowSelfSignedCert 是我的BOOL变量。只需添加到您的NSURLConnection委托中,您就可以在每个连接上快速绕过SSL检查。

- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)space {
     if([[space authenticationMethod] isEqualToString:NSURLAuthenticationMethodServerTrust]) {
          if(shouldAllowSelfSignedCert) {
               return YES; // Self-signed cert will be accepted
          } else {
               return NO;  // Self-signed cert will be rejected
          }
          // Note: it doesn't seem to matter what you return for a proper SSL cert
          //       only self-signed certs
     }
     // If no other authentication is required, return NO for everything else
     // Otherwise maybe YES for NSURLAuthenticationMethodDefault and etc.
     return NO;
}

11
在iOS 9中,所有无效或自签名证书的SSL连接将失败。这是iOS 9.0或更高版本以及OS X 10.11及更高版本中的新App Transport Security功能的默认行为。
您可以在Info.plist中通过在NSAppTransportSecurity字典中设置NSAllowsArbitraryLoadsYES来覆盖此行为。然而,我建议仅出于测试目的覆盖此设置。

enter image description here

有关信息,请参阅应用传输技术说明此处


唯一解决方案对我起作用了,我没有办法改变Firebase框架以适应我的需求,这解决了问题,谢谢! - Yitzchak
现在我看到谷歌要求在Firebase中的Admob(iOS)中设置NSAllowArbitraryLoads = YES。https://firebase.google.com/docs/admob/ios/ios9 - Yitzchak

7
由Nathan de Vries发布的类别解决方法可以通过AppStore私有API检查,并且在您无法控制NSUrlConnection对象的情况下非常有用。其中一个例子是NSXMLParser,它将打开您提供的URL,但不会公开NSURLRequestNSURLConnection
在iOS 4中,这种解决方法似乎仍然有效,但只适用于设备,模拟器不再调用allowsAnyHTTPSCertificateForHost:方法。

6

您必须使用NSURLConnectionDelegate以允许HTTPS连接,并且在iOS8中有新的回调函数。

不推荐使用:

connection:canAuthenticateAgainstProtectionSpace:
connection:didCancelAuthenticationChallenge:
connection:didReceiveAuthenticationChallenge:

需要声明以下内容:

connectionShouldUseCredentialStorage: - 用于确定URL加载器是否应使用凭据存储来验证连接。

connection:willSendRequestForAuthenticationChallenge: - 告诉代理将发送请求来进行身份验证。

使用 willSendRequestForAuthenticationChallenge,您可以像使用弃用的方法一样使用 challenge,例如:

// Trusting and not trusting connection to host: Self-signed certificate
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];

请问您能否检查一下这个链接:https://dev59.com/bLTma4cB1Zd3GeqP8Io9 - nr5

3
我发布了一些基于他人工作的代码片段(我已注明),它可以让您正确地对自动生成的证书进行身份验证(以及如何获得免费证书-请参见Cocoanetics底部的评论)。
我的代码在这里:github

请问您能否检查一下这个链接:https://dev59.com/bLTma4cB1Zd3GeqP8Io9 - nr5

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接