简体   繁体   English

出于开发目的,如何使用 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?

I am developing an iPhone app.我正在开发一个 iPhone 应用程序。 During development , I need to connect to a server that's using a self-signed SSL certificate.在开发期间,我需要连接到使用自签名 SSL 证书的服务器。 I'm pretty certain - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler is my opportunity to write some exception code to allow this.我很确定- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler是我编写一些异常代码来实现这一点的机会。 However, I can't find any resources that tell me how to do this.但是,我找不到任何资源可以告诉我如何执行此操作。 I can see the following error in the log:我可以在日志中看到以下错误:

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

In addition to this, when I NSLog(@"error = %@", error);除此之外,当我NSLog(@"error = %@", error); from within the above delegate method I get:从上面的委托方法中我得到:

Error Domain=NSURLErrorDomain Code=-1202 "The certificate for this server is invalid. You might be connecting to a server that is pretending to be api.mydevelopmenturl.example which could put your confidential information at risk."错误域=NSURLErrorDomain 代码=-1202 “此服务器的证书无效。您可能正在连接到伪装成api.mydevelopmenturl.example的服务器,这可能会使您的机密信息面临风险。” UserInfo=0x10cbdbcf0 {NSUnderlyingError=0x112ec9730 "The certificate for this server is invalid. You might be connecting to a server that is pretending to be api.mydevelopmenturl.example which could put your confidential information at risk.", NSErrorFailingURLStringKey=https://api.mydevelopmenturl.example/posts, NSErrorFailingURLKey=https://api.mydevelopmenturl.example/posts, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x112e5a020>, NSLocalizedDescription=The certificate for this server is invalid. UserInfo=0x10cbdbcf0 {NSUnderlyingError=0x112ec9730 "此服务器的证书无效。您可能正在连接到伪装成api.mydevelopmenturl.example的服务器,这可能会使您的机密信息面临风险。", NSErrorFailingURLStringKey=https:// api.mydevelopmenturl.example/posts, NSErrorFailingURLKey=https://api.mydevelopmenturl.example/posts, NSLocalizedRecoverySuggestion=您仍然想连接到服务器吗?, NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x112e5a020>, NSLocalizedDescription=此服务器的证书是无效的。 You might be connecting to a server that is pretending to be api.mydevelopmenturl.example which could put your confidential information at risk.}您可能正在连接到伪装成api.mydevelopmenturl.example的服务器,这可能会使您的机密信息面临风险。}

Any ideas on how to resolve this issue?关于如何解决这个问题的任何想法? Please post code as I've read the conceptual docs and I don't understand them.请发布代码,因为我已经阅读了概念文档,但我不理解它们。 Here's an example of one that's beyond me: https://developer.apple.com/library/content/technotes/tn2232/_index.html这是我无法理解的示例: https ://developer.apple.com/library/content/technotes/tn2232/_index.html

This works for me:这对我有用:

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.example"]){
      NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
      completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
    }
  }
}

Apple has a Technical Note 2232 which is quite informative and explains in detail HTTPS server trust evaluation . Apple 有一份技术说明 2232 ,内容丰富,详细解释了HTTPS 服务器信任评估

In this case error -1202 in the NSURLErrorDomain domain is NSURLErrorServerCertificateUntrusted , which means that server trust evaluation has failed.在这种情况下, NSURLErrorDomain域中的错误 -1202 是NSURLErrorServerCertificateUntrusted ,这意味着服务器信任评估失败。 You might also receive a variety of other errors;您可能还会收到各种其他错误; Appendix A: Common Server Trust Evaluation Errors lists the most common ones. 附录 A:常见服务器信任评估错误列出了最常见的错误。

From the Technical Note:从技术说明:

In most cases the best way to resolve a server trust evaluation failure is to fix the server.在大多数情况下,解决服务器信任评估失败的最佳方法是修复服务器。 This has two benefits: it offers the best security and it reduces the amount of code you have to write.这有两个好处:它提供了最好的安全性,它减少了你必须编写的代码量。 The remainder of this technote describes how you can diagnose server trust evaluation failures and, if it's not possible to fix the server, how you can customize server trust evaluation to allow your connection to proceed without completely undermining the user's security.本技术说明的其余部分描述了如何诊断服务器信任评估失败,以及如果无法修复服务器,如何自定义服务器信任评估以允许您的连接继续进行,而不会完全破坏用户的安全。

The particular bit that is germane to this question is the section on NSURLSession server trust evaluation :与这个问题密切相关的特定部分是关于NSURLSession 服务器信任评估的部分:

NSURLSession allows you to customize HTTPS server trust evaluation by implementing the -URLSession:didReceiveChallenge:completionHandler: delegate method. NSURLSession允许您通过实现-URLSession:didReceiveChallenge:completionHandler:委托方法来自定义 HTTPS 服务器信任评估。 To customize HTTPS server trust evaluation, look for a challenge whose protection space has an authentication method of NSURLAuthenticationMethodServerTrust .要自定义 HTTPS 服务器信任评估,请查找其保护空间具有NSURLAuthenticationMethodServerTrust身份验证方法的质询。 For those challenges, resolve them as described below.对于这些挑战,请按如下所述解决它们。 For other challenges, the ones that you don't care about, call the completion handler block with the NSURLSessionAuthChallengePerformDefaultHandling disposition and a NULL credential.对于其他挑战,您不关心的挑战,使用NSURLSessionAuthChallengePerformDefaultHandling处置和 NULL 凭证调用完成处理程序块。

When dealing with the NSURLAuthenticationMethodServerTrust authentication challenge, you can get the trust object from the challenge's protection space by calling the -serverTrust method.在处理 NSURLAuthenticationMethodServerTrust 身份验证质询时,您可以通过调用 -serverTrust 方法从质询的保护空间中获取信任对象。 After using the trust object to do your own custom HTTPS server trust evaluation, you must resolve the challenge in one of two ways:使用信任对象执行您自己的自定义 HTTPS 服务器信任评估后,您必须通过以下两种方式之一解决质询:

If you want to deny the connection, call the completion handler block with the NSURLSessionAuthChallengeCancelAuthenticationChallenge disposition and a NULL credential.如果要拒绝连接,请使用NSURLSessionAuthChallengeCancelAuthenticationChallenge处置和 NULL 凭据调用完成处理程序块。

If you want to allow the connection, create a credential from your trust object (using +[NSURLCredential credentialForTrust:] ) and call the completion handler block with that credential and the NSURLSessionAuthChallengeUseCredential disposition.如果您想允许连接,请从您的信任对象创建一个凭证(使用+[NSURLCredential credentialForTrust:] )并使用该凭证和NSURLSessionAuthChallengeUseCredential处置调用完成处理程序块。

The upshot of all this is that if you implement the following delegate method, you can override server trust for a particular server:所有这一切的结果是,如果您实现以下委托方法,您可以覆盖特定服务器的服务器信任:

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
{
    if([challenge.protectionSpace.authenticationMethod
                           isEqualToString:NSURLAuthenticationMethodServerTrust])
    {
        if([challenge.protectionSpace.host
                           isEqualToString:@"domaintooverride.example"])
        {
            NSURLCredential *credential =
                          [NSURLCredential credentialForTrust:
                                          challenge.protectionSpace.serverTrust];
            completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
        }
        else
            completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
    }
}

Note that you have to handle both the case of the host matching the one you want to override and all other cases.请注意,您必须同时处理与您要覆盖的主机匹配的情况以及所有其他情况。 If you don't handle the "all other cases" part, the behavior result is undefined.如果您不处理“所有其他情况”部分,则行为结果是未定义的。

Find a trusted SSL certificate authority online that's offering a free 90 day trial for new certificates.在线查找可提供 90 天免费试用新证书的可信 SSL 证书颁发机构。 Install the certificate on your server.在您的服务器上安装证书。 You now have 90 days to develop your app to a point where you can make a decision as to whether or not it's worth it to pay money to "renew" the certificate.您现在有 90 天的时间来开发您的应用程序,直到您可以决定是否值得花钱“更新”证书。 This is the best answer for me since my decision to use the self-signed certificate was financially motivated and 90 days gives me enough time develop my app to a point where I can decide if it's worth it to spend money on an SSL certificate or not.这对我来说是最好的答案,因为我决定使用自签名证书是出于经济动机,并且 90 天给了我足够的时间来开发我的应用程序,直到我可以决定是否值得花钱购买 SSL 证书. This approach avoids having to deal with the security implications of running a codebase that is tweaked to accept self-signed certificates.这种方法避免了必须处理运行代码库的安全隐患,该代码库经过调整以接受自签名证书。 Sweet!甜的! Yay for bootstrapping!是的引导!

Do yourself a huge favour and don't.帮自己一个大忙,不要。

Start by reading the paper The most dangerous code in the world: validating SSL certificates in non-browser software , especially section 10, "Breaking or disabling certificate validation".首先阅读论文世界上最危险的代码:在非浏览器软件中验证 SSL 证书,特别是第 10 节“破坏或禁用证书验证”。 It specifically calls out a Cocoa-related blog that specifically describes how to do what you ask.它专门调用了一个与 Cocoa 相关的博客,该博客专门描述了如何按照您的要求进行操作。

But don't.但是不要。 Disabling SSL certificate checking is like introducing a ticking time bomb into your app.禁用 SSL 证书检查就像在您的应用程序中引入了定时炸弹。 Sometime, someday, it will accidentally be left enabled, and a build will get into the wild.某天,某天,它会意外地被启用,并且构建将进入野外。 And on that day, your users will be put at serious risk.在那一天,您的用户将面临严重风险。

Instead you should use a certificate, signed with an intermediate cert that you can install and trust on that specific device, which will allow the SSL validation to succeed without endangering any other device than your own (and only then, temporarily).相反,您应该使用一个使用中间证书签名的证书,您可以在该特定设备上安装和信任该证书,这将允许 SSL 验证成功,而不会危及您自己的任何其他设备(并且仅在那时,暂时)。

For Swift 3.0 / 4对于 Swift 3.0 / 4

If you would just like to allow any kind of self-signed certificates, you could use the following approach, to implement an URLSessionDelegate.如果您只想允许任何类型的自签名证书,您可以使用以下方法来实现 URLSessionDelegate。 Apple provides additional information of how to use the URLSessionDelegate for all kinds of authentication methods: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/URLLoadingSystem/Articles/AuthenticationChallenges.html Apple 提供了有关如何将 URLSessionDelegate 用于各种身份验证方法的附加信息: https ://developer.apple.com/library/content/documentation/Cocoa/Conceptual/URLLoadingSystem/Articles/AuthenticationChallenges.html

At first implement the delegate method and assign an according delegate:首先实现委托方法并分配一个相应的委托:

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

Now implement the delegate's method https://developer.apple.com/documentation/foundation/nsurlsessiondelegate/1409308-urlsession?language=objc现在实现委托的方法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)
    }
}

Still, you could adapt the acceptance of any self-signed cert for your provided domain to match to a very specific one.不过,您可以调整对您提供的域的任何自签名证书的接受,以匹配一个非常具体的证书。 Make sure you added this certificate before to your build targets bundle.确保您之前将此证书添加到您的构建目标包中。 I named it here "cert.cer"我在这里将其命名为“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)
}

Same as friherd's solution but in swift:与 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);
    }
}

just need add .cer to SecTrust and it pass on ATS只需将 .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)
    }
}

update xcode 9更新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()

the delegate task委托任务

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

        }
    }

Here is the solution that worked for me.这是对我有用的解决方案。 You need to accept the connection in through the connection's delegate including both messages:您需要通过连接的委托接受连接,包括两条消息:

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

Please note that with doing this, you're not checking the trustability of the certificate, so only the SSL encryption of the HTTPS connection is interesting, but the signing authority is not taking into consideration here, which can decrease security.请注意,这样做并没有检查证书的可信度,因此只有 HTTPS 连接的 SSL 加密是有趣的,但这里没有考虑签名权限,这会降低安全性。

This Works fine for me to by pass self-signed :这对我来说很好,可以通过自签名:

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

Perhaps a better way is to provide the user with the opportunity to accept the certificate confirming (visually) that the URL is accurate for the service being accessed.也许更好的方法是为用户提供接受证书的机会,以确认(视觉上)该 URL 对于正在访问的服务是准确的。 For example, if the host is entered into some app setting, test at the user's entry and let the user decide right there.例如,如果主机被输入到某个应用程序设置中,请在用户的输入处进行测试,并让用户在那里做出决定。

Consider that this "user confirm" tactic is used by Safari, thus condoned by Apple, it would make sense that it would be employed logically for other apps.考虑到这种“用户确认”策略被 Safari 使用,因此被 Apple 纵容,它在逻辑上适用于其他应用程序是有道理的。

Suggest digging into NSErrorRecoveryAttempting (am doing no myself) http://apple.co/22Au1GR建议深入研究 NSErrorRecoveryAttempting(我自己不做) http://apple.co/22Au1GR

Get the host confirmed, then take the individual URL exclusion route mentioned herewithin.确认主机,然后采取本文中提到的个人 URL 排除路线。 Depending upon the implementation it may also make sense to store the host as an exclusion for future reference.根据实现,将主机存储为排除项以供将来参考也可能有意义。

This seems like something Apple would have implemented by nature in Cocoa but as of yet, I have not found an 'easy button'.这似乎是 Apple 在 Cocoa 中自然实现的功能,但到目前为止,我还没有找到一个“简单的按钮”。 Would have liked a "kLetUserDecide" flag on something in NSURL or NSURLSession instead of everyone having to implement the delegate method as well as the NSErrorRecoveryAttempting protocol.本来希望在 NSURL 或 NSURLSession 中的某些东西上有一个“kLetUserDecide”标志,而不是每个人都必须实现委托方法以及 NSErrorRecoveryAttempting 协议。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM