出于开发目的,如何使用 iOS 7 的 NSURLSession 及其委托方法系列接受自签名 SSL 证书?

Posted

技术标签:

【中文标题】出于开发目的,如何使用 iOS 7 的 NSURLSession 及其委托方法系列接受自签名 SSL 证书?【英文标题】:How do I accept a self-signed SSL certificate using iOS 7's NSURLSession and its family of delegate methods for development purposes? 【发布时间】:2013-10-30 16:39:24 【问题描述】:

我正在开发一个 iPhone 应用程序。 在开发过程中,我需要连接到使用自签名 SSL 证书的服务器。我很确定- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler 是我编写一些异常代码来实现这一点的机会。但是,我找不到任何资源可以告诉我如何执行此操作。我可以在日志中看到以下错误:

NSURLConnection/CFURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813)

除此之外,当我在上述委托方法中NSLog(@"error = %@", error); 时,我得到:

Error Domain=NSURLErrorDomain Code=-1202 "证书为 此服务器无效。您可能正在连接的服务器是 假装是“api.mydevelopmenturl.com”,这可能会让你的 机密信息有风险。” UserInfo=0x10cbdbcf0 NSUnderlyingError=0x112ec9730 "此服务器的证书是 无效的。您可能正在连接到假装的服务器 “api.mydevelopmenturl.com” 可以放置您的机密信息 有风险。", NSErrorFailingURLStringKey=https://api.mydevelopmenturl.com/posts, NSErrorFailingURLKey=https://api.mydevelopmenturl.com/posts, NSLocalizedRecoverySuggestion=你想连接到 还是服务器?,NSURLErrorFailingURLPeerTrustErrorKey=, NSLocalizedDescription=此服务器的证书无效。 您可能正在连接到假装的服务器 “api.mydevelopmenturl.com” 可能会将您的机密信息 有风险的信息。

关于如何解决此问题的任何想法?请发布代码,因为我已经阅读了概念文档,但我不理解它们。这是我无法理解的示例:https://developer.apple.com/library/content/technotes/tn2232/_index.html

【问题讨论】:

【参考方案1】:

这对我有用:

NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:Nil];
...
...
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
  if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
    if([challenge.protectionSpace.host isEqualToString:@"mydomain.com"])
      NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
      completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
    
  

【讨论】:

赞成,因为回答了这个问题。虽然只有在你知道自己在做什么的情况下才应该这样做。 是的,人们不认为测试服务器会有自签名证书。 ios 9.2 开始,这已经不够了。您还需要为域添加应用程序传输安全例外。 ***.com/a/32525737/85337 在这方面做得很好。 Apple 会允许在其商店中的生产应用中使用此功能吗? 请注意,这意味着所有其他域都将被完全忽略!您可能应该为 else 案例添加对完成处理程序的调用。【参考方案2】:

Apple 有一个Technical Note 2232,内容丰富,并详细解释了HTTPS server trust evaluation。

在这种情况下,NSURLErrorDomain 域中的错误 -1202 是 NSURLErrorServerCertificateUntrusted,这意味着服务器信任评估失败。您可能还会收到各种其他错误; Appendix A: Common Server Trust Evaluation Errors 列出了最常见的。

来自技术说明:

在大多数情况下,解决服务器信任评估的最佳方法 失败是修复服务器。这有两个好处:它提供 最好的安全性,它减少了您必须编写的代码量。这 本技术说明的其余部分描述了如何诊断服务器信任 评估失败,如果无法修复服务器,如何 您可以自定义服务器信任评估以允许您连接到 在不完全破坏用户安全的情况下继续操作。

与这个问题密切相关的特定部分是NSURLSession server trust evaluation 上的部分:

NSURLSession 允许您通过以下方式自定义 HTTPS 服务器信任评估 实施-URLSession:didReceiveChallenge:completionHandler: 委托方法。要自定义 HTTPS 服务器信任评估,请查找 一个挑战,其保护空间的验证方法为 NSURLAuthenticationMethodServerTrust。对于这些挑战,解决 它们如下所述。对于其他挑战,那些你没有 关心,调用完成处理程序块 NSURLSessionAuthChallengePerformDefaultHandling 处置和 NULL 凭证。

当处理 NSURLAuthenticationMethodServerTrust 身份验证质询,您可以从 通过调用 -serverTrust 方法来挑战的保护空间。后 使用信任对象来做你自己的自定义 HTTPS 服务器信任 评估,您必须通过以下两种方式之一解决挑战:

如果你想拒绝连接,调用完成处理程序块 NSURLSessionAuthChallengeCancelAuthenticationChallenge 性格 和一个 NULL 凭证。

如果您想允许连接,请从您的 信任对象(使用+[NSURLCredential credentialForTrust:])和 使用该凭据调用完成处理程序块,并且 NSURLSessionAuthChallengeUseCredential 性格。

所有这一切的结果是,如果您实现以下委托方法,您可以覆盖特定服务器的服务器信任:

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler

    if([challenge.protectionSpace.authenticationMethod
                           isEqualToString:NSURLAuthenticationMethodServerTrust])
    
        if([challenge.protectionSpace.host
                           isEqualToString:@"domaintoverride.com"])
        
            NSURLCredential *credential = 
                          [NSURLCredential credentialForTrust:
                                          challenge.protectionSpace.serverTrust];
            completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
        
        else
            completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
    

请注意,您必须处理 both 主机匹配您要覆盖的主机的情况所有其他情况。如果您不处理“所有其他情况”部分,则行为结果是未定义的。

【讨论】:

如果不调用回调,会话将不会继续。它要么永远等待,要么超时。所以我不会说它是未定义的,因为“不会继续”是对正在发生的事情的定义。 我可以使用challenge.proposedCredential而不是从serverTrust构造一个URLCredential吗? 你能检查一下吗:***.com/q/56627757/1364053【参考方案3】:

在线查找可提供 90 天免费试用新证书的可信 SSL 证书颁发机构。在您的服务器上安装证书。您现在有 90 天的时间来开发您的应用程序,直到您可以决定是否值得花钱“更新”证书。这对我来说是最好的答案,因为我决定使用自签名证书是出于经济动机,并且 90 天给了我足够的时间来开发我的应用程序,直到我可以决定是否值得花钱购买 SSL 证书.这种方法避免了必须处理运行经过调整以接受自签名证书的代码库的安全隐患。甜的!是的引导!

【讨论】:

请您检查一下:***.com/q/56627757/1364053【参考方案4】:

帮自己一个巨大的个忙,不要。

首先阅读论文The most dangerous code in the world: validating SSL certificates in non-browser software,尤其是第 10 节“破坏或禁用证书验证”。它专门调用了一个与 Cocoa 相关的博客,该博客专门描述了如何按照您的要求进行操作。

但不要。禁用 SSL 证书检查就像在您的应用程序中引入定时炸弹。某天,某天,它会意外地被启用,并且构建将进入野外。在那一天,您的用户将面临严重风险。

相反,您应该使用一个使用中间证书签名的证书,您可以在该特定设备上安装和信任该证书,这将允许 SSL 验证成功,而不会危及除您自己的设备之外的任何其他设备(并且只是暂时的)。

【讨论】:

我同意!我和你一样有安全意识。我认为您的回答增加了这篇文章的价值,但是,我强调 开发目的 知道在任何其他环境中接受自签名证书都是不安全的。我想可能有办法只将我的自签名证书列入白名单?这样,即使意外发布到生产环境中,也不会使用户面临风险。我首先使用的是自签名证书,因为我不想购买真正的证书,直到我的应用程序被证明是值得的(即它不会被放弃)。这是给我的,不是专业项目。 @JohnErck 您可以将 iOS 设备上的中间证书“列入白名单”,该证书安装在系统级别,然后您可以从中签署(链接)叶子证书,叶子将验证成功地。好处是您不需要更改任何关于 NSURL 等调用的任何事情 - 并且不需要修改您的代码根本 好吧,当我编写我的应用程序时,我可能愿意禁用 SSL 以进行测试,因为我尚未在我的开发环境中安装有效且签名的 SSL 证书。因此,不回答问题就是不回答问题。 @thibaultd 解决问题比导致数据不安全的答案更重要。阅读链接的论文。 @ShaggyFrog 我在做这件事时遇到了一些问题。我正在尝试使用 AFNetworking,但也将满足于使用 NSURL 类的更基本的解决方案。你有什么机会可以帮助我吗?我基本上无法使用嵌入到应用程序中的根证书来验证我们的测试服务器发送的叶证书。【参考方案5】:

对于 Swift 3.0 / 4

如果您只想允许任何类型的自签名证书,您可以使用以下方法来实现 URLSessionDelegate。 Apple 提供了有关如何将 URLSessionDelegate 用于各种身份验证方法的附加信息:https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/URLLoadingSystem/Articles/AuthenticationChallenges.html

首先实现委托方法并分配一个相应的委托:

let urlSession = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
let task = urlSession.dataTask(with: urlRequest).resume()

现在实现委托的方法 https://developer.apple.com/documentation/foundation/nsurlsessiondelegate/1409308-urlsession?language=objc

func urlSession(_ session: URLSession, 
     didReceive challenge: URLAuthenticationChallenge, 
        completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) 

    guard challenge.previousFailureCount == 0 else 
        challenge.sender?.cancel(challenge)
        // Inform the user that the user name and password are incorrect
        completionHandler(.cancelAuthenticationChallenge, nil)
        return
    

    // Within your authentication handler delegate method, you should check to see if the challenge protection space has an authentication type of NSURLAuthenticationMethodServerTrust
    if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust
       // and if so, obtain the serverTrust information from that protection space.
       && challenge.protectionSpace.serverTrust != nil
       && challenge.protectionSpace.host == "yourdomain.com" 
        let proposedCredential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
        completionHandler(URLSession.AuthChallengeDisposition.useCredential, proposedCredential)
    

不过,您可以调整对您提供的域的任何自签名证书的接受,以匹配一个非常具体的证书。确保您之前将此证书添加到您的构建目标捆绑包中。我在这里将其命名为“cert.cer”

func urlSession(_ session: URLSession, 
     didReceive challenge: URLAuthenticationChallenge, 
        completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) 

    guard challenge.previousFailureCount == 0 else 
        challenge.sender?.cancel(challenge)
        // Inform the user that the user name and password are incorrect
        completionHandler(.cancelAuthenticationChallenge, nil)
        return
    

    if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust
       && challenge.protectionSpace.serverTrust != nil
       && challenge.protectionSpace.host == "yourdomain.com" 

        if let trust = challenge.protectionSpace.serverTrust,
           let pem = Bundle.main.url(forResource:"cert", withExtension: "cer"),
           let data = NSData(contentsOf: pem),
           let cert = SecCertificateCreateWithData(nil, data) 
            let certs = [cert]
            SecTrustSetAnchorCertificates(trust, certs as CFArray)
            var result=SecTrustResultType.invalid
            if SecTrustEvaluate(trust,&result)==errSecSuccess 
              if result==SecTrustResultType.proceed || result==SecTrustResultType.unspecified 
                let proposedCredential = URLCredential(trust: trust)
                completionHandler(.useCredential,proposedCredential)
                return
              
            

        
    
    completionHandler(.performDefaultHandling, nil)

【讨论】:

yessssssssss,这是在头痛数周后对我有用的解决方案 请修改最后一个代码示例,因为基本上您加载了证书,然后直接忽略它。您可以在添加certs.removeAll() 后检查它是否可以正常工作。该部分需要致电SecTrustEvaluate(trust,&result) @Dmitry 感谢您的评论,请随时使用论坛的“编辑”功能,这样您就可以获得您应得的信誉,也可以更好地在帖子中声明为什么会这样解决方案会更合适:-) @Lepidopteron 我建议进行编辑,尽管一直认为直接编辑某人的代码是不道德的。我在末尾添加了SecTrustEvaluate(trust,&result) 和默认completionHandler,因为要求始终以一种或另一种方式调用它。 这样做会从数据任务返回NSURLErrorCancelled 错误。有没有办法代替NSURLErrorServerCertificateUntrusted【参考方案6】:

与 friherd 的解决方案相同,但速度很快:

func URLSession(session: NSURLSession, task: NSURLSessionTask, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) 
    if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust
        let credential = NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!)
        completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential,credential);
    

【讨论】:

我可以使用challenge.proposedCredential而不是从serverTrust构造一个URLCredential吗?【参考方案7】:

只需将 .cer 添加到 SecTrust 并传递 ATS

class NSURLSessionPinningDelegate: NSObject, URLSessionDelegate 

    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void) 

        if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) 
            if let trust = challenge.protectionSpace.serverTrust,
               let pem = Bundle.main.path(forResource: "https", ofType: "cer"),
               let data = NSData(contentsOfFile: pem),
               let cert = SecCertificateCreateWithData(nil, data) 
                let certs = [cert]
                SecTrustSetAnchorCertificates(trust, certs as CFArray)

                completionHandler(URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust: trust))
                return
            
        

        // Pinning failed
        completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
    

【讨论】:

【参考方案8】:

更新 xcode 9

    var result:(message:String, data:Data?) = (message: "Fail", data: nil)
    var request = URLRequest(url: url)

    let sessionDelegate = SessionDelegate()
    let session = URLSession(configuration: .default, delegate: sessionDelegate, delegateQueue: nil)
    let task = session.dataTask(with: request)(data, response, error) in


    
    task.resume()

委托任务

    class SessionDelegate:NSObject, URLSessionDelegate
    

        func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) 
            if(challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust)
            
                print(challenge.protectionSpace.host) 
                if(challenge.protectionSpace.host == "111.11.11.11")
                
                    let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
                   completionHandler(URLSession.AuthChallengeDisposition.useCredential, credential)
                
            

        
    

【讨论】:

【参考方案9】:

这是对我有用的解决方案。 您需要通过连接的委托接受连接,包括两条消息:

- (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])
        [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];

    [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];

请注意,这样做并没有检查证书的可信度,因此只有 HTTPS 连接的 SSL 加密是有趣的,但这里没有考虑签名机构,这会降低安全性。

【讨论】:

@sairam 在你的代表中。实现 NSURLConnectionDelegate 并且您用作连接对象上的委托的类。通常它是处理你的连接对象的同一个对象。 OP 询问的是NSURLSession 而不是NSURLConnection,所以这无关紧要。【参考方案10】:

这对我来说很好,可以绕过自签名:

Delegate : NSURLSessionDelegate

- (void)URLSession:(NSURLSession *)session **task**:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler

    completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);

【讨论】:

使用NSURLSessionDelegate的委托方法 委托----“会话”“任务”“didReceiveChallenge”。使用“TASK”委托方法。 在你的委托方法中添加这个。:: completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); 根据我在其他地方读到的内容,打开 iOS 9 的 ATS,这些都不起作用。那正确吗?所有证书都必须经过 CA 签名,期间。【参考方案11】:

也许更好的方法是为用户提供接受证书的机会,以确认(视觉上)该 URL 对于正在访问的服务是准确的。例如,如果主机被输入到某个应用程序设置中,请在用户的输入处进行测试,并让用户在那里做出决定。

考虑到这种“用户确认”策略被 Safari 使用,因此被 Apple 纵容,它在逻辑上也适用于其他应用程序是有道理的。

建议深入研究 NSErrorRecoveryAttempting(我自己不做) http://apple.co/22Au1GR

确认主机,然后采用此处提到的单个 URL 排除路线。根据实现,将主机存储为排除项以供将来参考也可能有意义。

这似乎是 Apple 在 Cocoa 中自然实现的功能,但到目前为止,我还没有找到“简单按钮”。本来希望在 NSURL 或 NSURLSession 中的某些东西上有一个“kLetUserDecide”标志,而不是每个人都必须实现委托方法以及 NSErrorRecoveryAttempting 协议。

【讨论】:

以上是关于出于开发目的,如何使用 iOS 7 的 NSURLSession 及其委托方法系列接受自签名 SSL 证书?的主要内容,如果未能解决你的问题,请参考以下文章

出于开发目的,无法从本地主机连接到 AWS RDS Postgresql

如何在 iOS 7 中获得麦克风音量?

iOS开发网络篇--NSURLConnection

iOS开发网络篇—NSURLConnection基本使用

iOS 7 无法播放系统声音

iOS开发系列-NSURLConnection