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



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

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

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

错误域=NSURLError域代码=-1202"证书此服务器无效。您可能正在连接到假装api.mydevelopmenturl.example,这可能会让你机密信息面临风险。 用户信息=0x10cbdbcf0{NSUnderlyingError=0x112ec9730 "此服务器的证书是无效。您可能正在连接到伪装成 api.mydevelopmenturl.example可以放置您的机密信息", NSErrorFailingURLStringKey=https://api.mydevelopmenturl.example/posts,NSErrorFailingURLKey=https://api.mydevelopmenturl.example/posts,NSLocalizedRecoverySuggestion=是否要连接到server anyway?, NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef:>,NSLocalizedDescription=此服务器的证书无效。您可能正在连接到伪装成 api.mydevelopmenturl.example可以使您的机密存在风险的信息。

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

这对我有用:

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有一个技术说明2232,它非常有用,并详细解释了HTTPS服务器信任评估。

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

从技术说明:

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

与此问题密切相关的特定部分是有关NSURLSession服务器信任评估的部分:

NSURLSession允许您通过以下方式自定义HTTPS服务器信任评估实施-URLSession:didReceiveChallenge:completionHandler:委托方法。若要自定义 HTTPS 服务器信任评估,请查找保护空间的身份验证方法 NSURLAuthenticationMethodServerTrust .面对这些挑战,决心它们如下所述。对于其他挑战,那些你没有的挑战关心,使用 NSURLSessionAuthChallengePerformDefaultHandling处置和空凭据。

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

如果要拒绝连接,请使用NSURLSessionAuthChallengeCancelAuthenticationChallenge处置和空凭据。

如果要允许连接,请从信任对象(使用 +[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:@"domaintooverride.example"])
        {
            NSURLCredential *credential =
                          [NSURLCredential credentialForTrust:
                                          challenge.protectionSpace.serverTrust];
            completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
        }
        else
            completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
    }
}

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

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

帮自己一个忙,不要。

首先阅读论文世界上最危险的代码:在非浏览器软件中验证SSL证书,特别是第10节"破坏或禁用证书验证"。它特别提到了一个与可可相关的博客,专门描述了如何做你要求的事情。

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

相反,您应该使用证书,

使用可以在该特定设备上安装和信任的中间证书进行签名,这将使SSL验证成功,而不会危及您自己的设备以外的任何其他设备(并且只有这样,暂时)。

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

不过,您可以调整对所提供的域的任何自签名证书的接受,以匹配非常具体的证书。请确保在之前将此证书添加到生成目标捆绑包。我在这里将其命名为"证书.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)
}

与 friherd 的解决方案相同,但在 swift 中:

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

只需要将.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)
    }
}

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

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

- (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加密很有趣,但此处没有考虑签名机构,这可能会降低安全性。

这对我来说

很好用,通过自签名:

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]);
}
<</div> div class="one_answers">

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

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

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

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

这似乎是苹果在可可中自然会实现的东西,但到目前为止,我还没有找到一个"简单的按钮"。 希望在NSURL或NSURLSession中的某些内容上使用"kLetUserDecide"标志,而不是每个人都必须实现委托方法以及NSErrorRecoveryTry协议。

最新更新