【问题标题】:How to use NSURLConnection to connect with SSL for an untrusted cert?如何使用 NSURLConnection 与 SSL 连接以获得不受信任的证书?
【发布时间】:2010-10-30 07:39:56
【问题描述】:

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

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

除非证书是自签名证书会出错Error Domain=NSURLErrorDomain Code=-1202 UserInfo=0xd29930 "untrusted server certificate". 有没有办法将其设置为接受连接(就像在浏览器中可以按接受一样)或绕过它?

【问题讨论】:

    标签: ios objective-c https ssl-certificate app-transport-security


    【解决方案1】:

    有一个支持的 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(很多时候),在必要时向用户显示对话框等之后。

    【讨论】:

    • 非常感谢,它运行良好。如果您想接受任何 https 站点,只需删除两个 if 并仅保留 didReceiveAuthentificationChallenge 回调中的 useCendential 部分。
    • 什么是trustedHosts,其中n对象是如何定义的
    • Ameya,它将是 NSString 对象的 NSArray。字符串是主机名,例如 @"google.com"。
    • 这段代码运行良好。但请注意,拥有有效证书的全部目的是防止中间人攻击。因此请注意,如果您使用此代码,有人可能会欺骗所谓的“受信任主机”。您仍然可以获得 SSL 的数据加密功能,但您会失去主机身份验证功能。
    • 这些方法现在被认为在 iOS 5.0 和 Mac OS X 10.6 中已弃用。应该改用-(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge 方法。
    【解决方案2】:

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

    【讨论】:

    • Tim,你可能会发现自己出于其他原因想要使用异步(比如能够显示进度条),我发现除了最简单的请求之外的所有请求都是我的方式。所以也许你应该现在就实现 Async 并在以后省去麻烦。
    • 见这个实现(但使用 [r setValidatesSecureCertificate:NO]; ):stackoverflow.com/questions/7657786/…
    • 抱歉,我把这个话题带回来了。但是自从 iOS 5 引入了 ARC 功能。我现在怎样才能完成这项工作?
    • 你能检查一下吗:stackoverflow.com/q/56627757/1364053
    【解决方案3】:

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

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

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

    生产主机不应为obvious reasons 使用不受信任的证书。

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

    为了测试目的而接受不受信任的证书的推荐方法是将证书颁发机构 (CA) 证书导入您的 iOS 模拟器或 iOS 设备上,该证书签署了证书。我写了一篇快速博客文章,演示了如何在 iOS 模拟器中执行此操作:

    accepting untrusted certificates using the ios simulator

    【讨论】:

    • 很棒的东西人。我同意,很容易忘记禁用这个特殊的应用程序逻辑来接受任何不受信任的证书。
    • “理想情况下,iOS 应用程序需要接受不受信任的证书时应该只有两种情况。” - 在固定证书时拒绝“声称”的好证书怎么样?授予:Dignotar(pwn'd)和 Trustwave(MitM 成名)。
    • 完全同意您关于忘记删除代码的说法。具有讽刺意味的是,在代码中进行这种更改比让模拟器接受自签名证书要容易得多。
    【解决方案4】:

    NSURLRequest 有一个名为setAllowsAnyHTTPSCertificate:forHost: 的私有方法,它可以完全按照您的意愿进行操作。您可以通过类别在NSURLRequest 上定义allowsAnyHTTPSCertificateForHost: 方法,并将其设置为为您要覆盖的主机返回YES

    【讨论】:

    • 适用于未记录 API 的通常警告......但很高兴知道这是可能的。
    • 是的,当然。我添加了另一个不涉及使用私有 API 的答案。
    • 当你使用 "NSURLConnection sendSynchronousRequest:" 时是否有效?
    【解决方案5】:

    为了补充接受的答案,为了更好的安全性,您可以将您的服务器证书或您自己的根 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];
    }
    

    【讨论】:

      【解决方案6】:

      对此我不以为然,but this one I found 非常适合我的需求。 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;
      }
      

      【讨论】:

        【解决方案7】:

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

        您可以在Info.plist 中覆盖此行为,方法是在NSAppTransportSecurity 字典中将NSAllowsArbitraryLoads 设置为YES但是,我建议仅出于测试目的覆盖此设置。

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

        【讨论】:

        • 唯一对我有用的解决方案,我无法更改 Firebase 框架以满足我的需求,解决了,谢谢!
        • 现在我看到 Google 要求 NSAllowArbitraryLoads = YES 用于 Admob(在 Firebase 中)。 firebase.google.com/docs/admob/ios/ios9
        【解决方案8】:

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

        在 iOS 4 中,解决方法似乎仍然有效,但仅在设备上,模拟器不再调用 allowsAnyHTTPSCertificateForHost: 方法。

        【讨论】:

          【解决方案9】:

          您必须使用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];
          

          【讨论】:

          【解决方案10】:

          我发布了一些要点代码(基于我注意到的其他人的工作),可以让您正确地针对自生成的证书进行身份验证(以及如何获得免费证书 - 请参阅Cocoanetics 的 cmets 底部)

          我的代码在这里github

          【讨论】:

          【解决方案11】:

          如果您想继续使用 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 Synchronous Connection

          【讨论】:

            【解决方案12】:

            您可以使用此代码

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

            【讨论】:

              【解决方案13】:

              使用AFNetworking,我已成功使用以下代码使用 https 网络服务,

              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);
              }];
              

              【讨论】:

                猜你喜欢
                • 2014-12-20
                • 1970-01-01
                • 1970-01-01
                • 2010-11-15
                • 2019-04-04
                • 2016-01-15
                • 1970-01-01
                相关资源
                最近更新 更多