[英]How do I accept a self-signed SSL certificate using iOS 7's NSURLSession and its family of delegate methods for development purposes?
我正在开发一个 iPhone 应用程序。 在开发期间,我需要连接到使用自签名 SSL 证书的服务器。 我很确定- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
是我编写一些异常代码来实现这一点的机会。 但是,我找不到任何资源可以告诉我如何执行此操作。 我可以在日志中看到以下错误:
NSURLConnection/CFURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813)
除此之外,当我NSLog(@"error = %@", error);
从上面的委托方法中我得到:
错误域=NSURLErrorDomain 代码=-1202 “此服务器的证书无效。您可能正在连接到伪装成
api.mydevelopmenturl.example
的服务器,这可能会使您的机密信息面临风险。” UserInfo=0x10cbdbcf0 {NSUnderlyingError=0x112ec9730 "此服务器的证书无效。您可能正在连接到伪装成api.mydevelopmenturl.example
的服务器,这可能会使您的机密信息面临风险。", NSErrorFailingURLStringKey=https:// api.mydevelopmenturl.example/posts, NSErrorFailingURLKey=https://api.mydevelopmenturl.example/posts, NSLocalizedRecoverySuggestion=您仍然想连接到服务器吗?, NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x112e5a020>, 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
允许您通过实现-URLSession:didReceiveChallenge:completionHandler:
委托方法来自定义 HTTPS 服务器信任评估。 要自定义 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:@"domaintooverride.example"])
{
NSURLCredential *credential =
[NSURLCredential credentialForTrust:
challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
}
else
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}
}
请注意,您必须同时处理与您要覆盖的主机匹配的情况以及所有其他情况。 如果您不处理“所有其他情况”部分,则行为结果是未定义的。
在线查找可提供 90 天免费试用新证书的可信 SSL 证书颁发机构。 在您的服务器上安装证书。 您现在有 90 天的时间来开发您的应用程序,直到您可以决定是否值得花钱“更新”证书。 这对我来说是最好的答案,因为我决定使用自签名证书是出于经济动机,并且 90 天给了我足够的时间来开发我的应用程序,直到我可以决定是否值得花钱购买 SSL 证书. 这种方法避免了必须处理运行代码库的安全隐患,该代码库经过调整以接受自签名证书。 甜的! 是的引导!
帮自己一个大忙,不要。
首先阅读论文世界上最危险的代码:在非浏览器软件中验证 SSL 证书,特别是第 10 节“破坏或禁用证书验证”。 它专门调用了一个与 Cocoa 相关的博客,该博客专门描述了如何按照您的要求进行操作。
但是不要。 禁用 SSL 证书检查就像在您的应用程序中引入了定时炸弹。 某天,某天,它会意外地被启用,并且构建将进入野外。 在那一天,您的用户将面临严重风险。
相反,您应该使用一个使用中间证书签名的证书,您可以在该特定设备上安装和信任该证书,这将允许 SSL 验证成功,而不会危及您自己的任何其他设备(并且仅在那时,暂时)。
如果您只想允许任何类型的自签名证书,您可以使用以下方法来实现 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()
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)
}
与 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);
}
}
只需将 .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]);
}
也许更好的方法是为用户提供接受证书的机会,以确认(视觉上)该 URL 对于正在访问的服务是准确的。 例如,如果主机被输入到某个应用程序设置中,请在用户的输入处进行测试,并让用户在那里做出决定。
考虑到这种“用户确认”策略被 Safari 使用,因此被 Apple 纵容,它在逻辑上适用于其他应用程序是有道理的。
建议深入研究 NSErrorRecoveryAttempting(我自己不做) http://apple.co/22Au1GR
确认主机,然后采取本文中提到的个人 URL 排除路线。 根据实现,将主机存储为排除项以供将来参考也可能有意义。
这似乎是 Apple 在 Cocoa 中自然实现的功能,但到目前为止,我还没有找到一个“简单的按钮”。 本来希望在 NSURL 或 NSURLSession 中的某些东西上有一个“kLetUserDecide”标志,而不是每个人都必须实现委托方法以及 NSErrorRecoveryAttempting 协议。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.