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

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

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

除了这个,当我NSLog(@"error = %@", error); 从上面的代理方法中我得到:

错误域= NSURLErrorDomain代码= -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

这适用于我:

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

苹果有一个技术说明2232 ,这是相当丰富的信息,并详细解释HTTPS服务器信任评估 。

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

从技术说明:

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

与这个问题密切相关的特定位是关于NSURLSession服务器信任评估的部分:

NSURLSession允许您通过实现-URLSession:didReceiveChallenge:completionHandler: delegate方法来自定义HTTPS服务器信任评估。 要自定义HTTPS服务器信任评估,请查找保护空间具有NSURLAuthenticationMethodServerTrustvalidation方法的NSURLAuthenticationMethodServerTrust 。 对于这些挑战,请按照以下说明解决。 对于其他挑战,您不关心的挑战,使用NSURLSessionAuthChallengePerformDefaultHandling处置和NULL凭据调用完成处理程序块。

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

如果要拒绝连接,请使用NSURLSessionAuthChallengeCancelAuthenticationChallenge处置和NULL凭据调用完成处理程序块。

如果要允许连接,请使用信任对象创build凭证(使用+[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); } } 

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

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

做你自己一个大忙,不要。

首先阅读论文世界上最危险的代码:在非浏览器软件中validationSSL证书 ,尤其是第10节“破坏或禁用证书validation”。 它专门调出一个cocoa相关的博客,专门描述如何去做你所要求的。

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

相反,您应该使用证书,并使用中间证书进行签名,您可以在该特定设备上安装并信任该证书,从而使SSLvalidation成功,而不会危及您自己(而且只是暂时)以外的任何其他设备。

一样的解决scheme,但在迅速:

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

对于Swift 3.0 / 4

如果您只想允许任何types的自签名证书,则可以使用以下方法来实现URLSessionDelegate。 Apple提供了有关如何使用URLSessionDelegate用于各种身份validation方法的其他信息: 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) } } 

尽pipe如此,您还是可以根据您提供的域名来接受任何自签名证书,以便与特定的域名相匹配。 确保你在构build目标包之前添加了这个证书。 我在这里命名为“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) let proposedCredential = URLCredential(trust: trust) completionHandler(URLSession.AuthChallengeDisposition.useCredential, proposedCredential) return } } } 

这工作对我来说很好,通过自签名:

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

也许更好的方法是为用户提供接受证书的机会(视觉上)确认URL对于被访问的服务是准确的。 例如,如果主机进入了某些应用程序设置,则在用户的input处进行testing,并让用户在那里决定。

考虑到Safari使用这种“用户确认”策略,因此被苹果公司所宽恕,这将是有道理的,它将被用于其他应用程序的逻辑。

build议挖掘NSErrorRecoveryAttempting(我自己不做) http://apple.co/22Au1GR

获取主机确认,然后采取这里提到的个人url排除路线。 根据实施情况,将主机存储为排除以供将来参考也是有意义的。

这似乎是苹果在Cocoa自然会实现的东西,但到目前为止,我还没有find一个“简单的button”。 本来想在NSURL或NSURLSession中使用“kLetUserDecide”标志,而不是每个人都必须实现委托方法以及NSErrorRecoveryAttempting协议。

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

这是为我工作的解决scheme。 您需要通过连接的委托接受连接,包括两个消息:

 - (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连接的SSLencryption很有意思,但是签名机构在这里没有考虑到,这会降低安全性。