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


300

我有以下简单的代码可以连接到SSL网页

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

如果证书是自签名证书,它将产生错误,但是Error Domain=NSURLErrorDomain Code=-1202 UserInfo=0xd29930 "untrusted server certificate".是否有办法将其设置为仍然接受连接(就像在浏览器中可以按accept一样)还是可以绕过它?

Answers:


415

有支持的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站点,只需删除两个if并在didReceiveAuthentificationChallenge回调中仅保留useCendential部分。
yonel 2010年

19
什么是TrustedHosts,其中n如何定义对象
Ameya,2010年

7
Ameya,那将是NSString对象的NSArray。字符串是主机名,例如@“ google.com”。
William Denniss 2010年

19
此代码运行良好。但是请注意,拥有有效证书的全部目的是为了防止中间人攻击。因此请注意,如果您使用此代码,则有人可以欺骗所谓的“可信主机”。您仍然可以获得SSL的数据加密功能,但是丢失了主机标识验证功能。
威廉·丹尼斯

42
从iOS 5.0和Mac OS X 10.6开始,这些方法现在被视为已弃用。-(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge应该改用该方法。
Andrew R.

36

如果您不愿意(或无法使用)私有API,则有一个称为ASIHTTPRequest的开源(BSD许可证)库,该库提供了较低层的包装CFNetwork APIs。他们最近引入了允许HTTPS connections通过-setValidatesSecureCertificate:API 使用自签名或不受信任的证书的功能。如果您不想提取整个库,则可以将源代码用作自己实现相同功能的参考。


2
蒂姆,无论如何,您可能会发现自己出于其他原因想要使用异步(例如能够显示进度栏),我发现除了最简单的请求外,其他所有方法都是这样。因此,也许您应该立即实施Async并在以后节省麻烦。
威廉·丹尼斯

看到这个的实现(但使用[R setValidatesSecureCertificate:NO]):stackoverflow.com/questions/7657786/...
山姆Brodkin

抱歉,我把这个话题重新备份了。但是自从iOS 5引入ARC功能以来。我现在该如何工作?
黎文彬

您能否检查一下:stackoverflow.com/q/56627757/1364053
nr5

33

理想情况下,应该只有两种情况,即iOS应用程序何时需要接受不受信任的证书。

方案A:您已连接到使用自签名证书的测试环境。

方案B:您正在HTTPS使用代理流量MITM Proxy like Burp Suite, Fiddler, OWASP ZAP, etc.,代理将返回由自签名CA签名的证书,以便代理能够捕获HTTPS流量。

由于明显的原因,生产主机绝不应使用不受信任的证书。

如果您需要让iOS模拟器接受不受信任的证书以进行测试,则强烈建议您不要更改应用程序逻辑,以禁用NSURLConnectionAPI 提供的内置证书验证。如果在不删除此逻辑的情况下将其发布给公众,则它很容易受到中间人攻击。

出于测试目的,接受不可信证书的推荐方法是将对证书进行签名的证书颁发机构(CA)证书导入到iOS Simulator或iOS设备上。我写了一篇简短的博客文章,演示了如何在iOS模拟器上执行此操作:

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


1
很棒的家伙。我同意,很容易忘记禁用此特殊应用程序逻辑以接受任何不受信任的证书。
Tomasz

“理想情况下,应该只有两种情况,即iOS应用程序何时需要接受不受信任的证书。” -固定证书时拒绝“已声明”好的证书如何?授予:Dignotar(pwn'd)和Trustwave(MitM成名)。
jww 2012年

完全同意您关于忘记删除代码的声明。具有讽刺意味的是,与让模拟器接受自签名证书相比,在代码中进行此更改要容易得多。
devios1 2013年

12

NSURLRequest有一个称为的私有方法setAllowsAnyHTTPSCertificate:forHost:,该方法可以完全满足您的需求。您可以通过类别定义allowsAnyHTTPSCertificateForHost:方法NSURLRequest,然后将其设置YES为要覆盖的主机返回。


关于未记录的API的常见警告适用...但是很高兴知道这是可能的。
斯蒂芬·达林顿

是的,绝对。我添加了另一个答案,该答案不涉及私有API的使用。
内森·德弗里斯

当您使用“ NSURLConnection sendSynchronousRequest:”时,该方法有效吗?
TimBüthe10年

11

为了补充已接受的答案,为了提高安全性,您可以将服务器证书或您自己的根CA证书添加到钥匙串中(https://stackoverflow.com/a/9941559/1432048),但是仅执行此操作不会使NSURLConnection有效自动验证您的自签名服务器。您仍然需要将以下代码添加到您的NSURLConnection委托中,它是从Apple示例代码AdvancedURLConnections复制而来,并且您需要从Apple示例代码向您的项目中添加两个文件(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];
}

10

我对此一无所获,但是我发现这对于我的需求来说确实非常有效。shouldAllowSelfSignedCert是我的BOOL变量。只需添加到您的NSURLConnection代理中,您就可以在每个连接的基础上快速绕开。

- (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;
}

10

在iOS 9中,所有无效或自签名证书的SSL连接都将失败。这是iOS 9.0或更高版本以及OS X 10.11和更高版本上新的App Transport Security功能的默认行为。

您可以Info.plist通过在字典中将设置NSAllowsArbitraryLoads为来覆盖此行为。但是,我建议仅出于测试目的而覆盖此设置。YESNSAppTransportSecurity

在此处输入图片说明

有关信息,请参阅此处的 App Transport技术说明。


唯一适用于我的解决方案,我无法更改Firebase框架以适应我的需求,因此解决了,谢谢!
Yitzchak

现在,我看到Google为Admob(在Firebase中)要求NSAllowArbitraryLoads = YES。firebase.google.com/docs/admob/ios/ios9
Yitzchak

6

Nathan de Vries发布的类别解决方法将通过AppStore私有API检查,在您无法控制NSUrlConnection对象的情况下非常有用。一个示例是NSXMLParser打开您提供的URL,但不公开NSURLRequest或的示例NSURLConnection

在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];

您能否检查一下:stackoverflow.com/q/56627757/1364053
nr5


2

如果您想继续使用sendSynchronousRequest我可以在以下解决方案中工作:

FailCertificateDelegate *fcd=[[FailCertificateDelegate alloc] init];

NSURLConnection *c=[[NSURLConnection alloc] initWithRequest:request delegate:fcd startImmediately:NO];
[c setDelegateQueue:[[NSOperationQueue alloc] init]];
[c start];    
NSData *d=[fcd getData];

您可以在这里看到它:Objective-C SSL同步连接


1

借助AFNetworking,我已成功使用以下代码使用了https webservice,

NSString *aStrServerUrl = WS_URL;

// Initialize AFHTTPRequestOperationManager...
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
manager.responseSerializer = [AFJSONResponseSerializer serializer];

[manager.requestSerializer setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
manager.securityPolicy.allowInvalidCertificates = YES; 
[manager POST:aStrServerUrl parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject)
{
    successBlock(operation, responseObject);

} failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
    errorBlock(operation, error);
}];

1

您可以使用此代码

-(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
     if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodServerTrust)
     {
         [[challenge sender] useCredential:[NSURLCredential credentialForTrust:[[challenge protectionSpace] serverTrust]] forAuthenticationChallenge:challenge];
     }
}

使用-connection:willSendRequestForAuthenticationChallenge:代替这些不推荐使用的方法

不推荐使用:

-(BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace  
-(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge 
-(void)connection:(NSURLConnection *)connection didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.