如何使用 iOS 7 的 NSURLSession 及其委托方法系列接受自签名 SSL 证书以用于开发目的?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/19507207/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do I accept a self-signed SSL certificate using iOS 7's NSURLSession and its family of delegate methods for development purposes?
提问by John Erck
I am developing an iPhone app. During development, I need to connect to a server that's using a self-signed SSL certificate. 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. However, I can't find any resources that tell me how to do this. I can see the following error in the log:
我正在开发一个 iPhone 应用程序。在开发过程中,我需要连接到使用自签名 SSL 证书的服务器。我很确定- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
我有机会编写一些异常代码来实现这一点。但是,我找不到任何资源告诉我如何执行此操作。我可以在日志中看到以下错误:
NSURLConnection/CFURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813)
In addition to this, when I NSLog(@"error = %@", error);
from within the above delegate method I get:
除此之外,当我NSLog(@"error = %@", error);
从上面的委托方法中我得到:
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.com” which could put your confidential information at risk." 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.com” which could put your confidential information at risk.", NSErrorFailingURLStringKey=https://api.mydevelopmenturl.com/posts, NSErrorFailingURLKey=https://api.mydevelopmenturl.com/posts, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, NSURLErrorFailingURLPeerTrustErrorKey=, NSLocalizedDescription=The certificate for this server is invalid. You might be connecting to a server that is pretending to be “api.mydevelopmenturl.com” which could put your confidential information at risk.}
错误域=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”的服务器,这可能会使您的机密信息处于危险之中。}
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
回答by friherd
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.com"]){
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
}
}
}
回答by memmons
Apple has a Technical Note 2232which 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. You might also receive a variety of other errors; Appendix A: Common Server Trust Evaluation Errorslists the most common ones.
在这种情况下,NSURLErrorDomain
域中的错误 -1202是NSURLErrorServerCertificateUntrusted
,这意味着服务器信任评估失败。您可能还会收到各种其他错误;附录 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. To customize HTTPS server trust evaluation, look for a challenge whose protection space has an authentication method ofNSURLAuthenticationMethodServerTrust
. 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 theNSURLSessionAuthChallengePerformDefaultHandling
disposition and a NULL credential.When dealing with the NSURLAuthenticationMethodServerTrust authentication challenge, you can get the trust object from the challenge's protection space by calling the -serverTrust method. After using the trust object to do your own custom HTTPS server trust evaluation, you must resolve the challenge in one of two ways:
If you want to deny the connection, call the completion handler block with the
NSURLSessionAuthChallengeCancelAuthenticationChallenge
disposition and a NULL credential.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 theNSURLSessionAuthChallengeUseCredential
disposition.
NSURLSession
允许您通过实现-URLSession:didReceiveChallenge:completionHandler:
委托方法来自定义 HTTPS 服务器信任评估。要自定义 HTTPS 服务器信任评估,请查找其保护空间的身份验证方法为NSURLAuthenticationMethodServerTrust
. 对于这些挑战,请按如下所述解决它们。对于其他挑战,您不关心的挑战,请使用NSURLSessionAuthChallengePerformDefaultHandling
处置和 NULL 凭证调用完成处理程序块 。在处理 NSURLAuthenticationMethodServerTrust 身份验证质询时,您可以通过调用 -serverTrust 方法从质询的保护空间中获取信任对象。使用 trust 对象执行您自己的自定义 HTTPS 服务器信任评估后,您必须通过以下两种方式之一解决挑战:
如果要拒绝连接,请使用
NSURLSessionAuthChallengeCancelAuthenticationChallenge
处置和 NULL 凭据调用完成处理程序块。如果您想允许连接,请从您的信任对象(使用
+[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:@"domaintoverride.com"])
{
NSURLCredential *credential =
[NSURLCredential credentialForTrust:
challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
}
else
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}
}
Note that you have to handle boththe case of the host matching the one you want to override andall other cases. If you don't handle the "all other cases" part, the behavior result is undefined.
请注意,你必须处理两个匹配要覆盖一个主机的情况下,与所有其他情况。如果不处理“所有其他情况”部分,则行为结果未定义。
回答by John Erck
Find a trusted SSL certificate authority online that's offering a free 90 day trial for new certificates. 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. 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. 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!
在线查找受信任的 SSL 证书颁发机构,该机构为新证书提供 90 天免费试用。在您的服务器上安装证书。您现在有 90 天的时间来开发您的应用程序,您可以决定是否值得花钱“续订”证书。这对我来说是最好的答案,因为我决定使用自签名证书是出于经济动机,90 天给了我足够的时间来开发我的应用程序,我可以决定是否值得花钱购买 SSL 证书. 这种方法避免了必须处理运行经过调整以接受自签名证书的代码库的安全隐患。甜的!耶!
回答by Shaggy Frog
Do yourself a hugefavour 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". It specifically calls out a Cocoa-related blog that specifically describes how to do what you ask.
首先阅读论文世界上最危险的代码:在非浏览器软件中验证 SSL 证书,尤其是第 10 节“破坏或禁用证书验证”。它专门调用了一个与 Cocoa 相关的博客,专门描述了如何做你所要求的。
But don't. Disabling SSL certificate checking is like introducing a ticking time bomb into your app. 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.
但是不要。禁用 SSL 证书检查就像在您的应用程序中引入一个定时炸弹。某天,某天,它会意外地保持启用状态,并且构建将变得疯狂。在那一天,您的用户将面临严重的风险。
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 验证成功,而不会危及除您自己的设备之外的任何其他设备(并且只有在那时才会危及您自己的设备)。
回答by Lepidopteron
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. 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
如果您只想允许任何类型的自签名证书,您可以使用以下方法来实现 URLSessionDelegate。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
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)
}
回答by eric
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);
}
}
回答by yycking
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)
}
}
回答by luhuiya
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)
}
}
}
}
回答by Riyaz shaik riyaz
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]);
}
回答by William Cerniuk
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. For example, if the host is entered into some app setting, test at the user's entry and let the user decide right there.
也许更好的方法是为用户提供接受证书的机会,以确认(视觉上)URL 对于所访问的服务是准确的。例如,如果主机进入某个应用程序设置,则在用户的入口处进行测试,然后让用户在那里做出决定。
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. Depending upon the implementation it may also make sense to store the host as an exclusion for future reference.
确认主机,然后采用此处提到的单个 URL 排除路由。根据实现,将主机存储为排除项以供将来参考也可能有意义。
This seems like something Apple would have implemented by nature in Cocoa but as of yet, I have not found an 'easy button'. 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.
这似乎是 Apple 自然会在 Cocoa 中实现的东西,但到目前为止,我还没有找到“简单按钮”。本来希望在 NSURL 或 NSURLSession 中的某个东西上有一个“kLetUserDecide”标志,而不是每个人都必须实现委托方法以及 NSErrorRecoveryAttempting 协议。