如何在 Cocoa 中使用 WebView 连接客户端证书?

发布于 2024-11-01 09:15:22 字数 609 浏览 6 评论 0原文

我正在尝试连接到需要客户端证书的服务器。 因此,浏览到此服务器时发生的正常事件流程是 Web 浏览器(Safari 和 Chrome)提示用户选择证书并重试操作。

那么如何在 Cocoa 项目的嵌入式 WebView 中实现这一点呢? 到目前为止,我已经确定该错误是在 didFailProvisionalLoadWithError 方法中引发的:

- (void)webView:(WebView *)sender didFailProvisionalLoadWithError:(NSError *)error forFrame:(WebFrame *)frame {
    NSLog(@"webView:didFailProvisionalLoadWithError:forFrame:");
    NSLog(@"    error = %@", error);
}

该错误确实是 error = Error Domain=NSURLErrorDomain Code=-1206 UserInfo=0x1006a8030 "The server “myserver.xxx”需要客户端证书。
但是如何显示一个对话框以便用户可以从钥匙串中选择证书?

I am trying to connect to a server that requires a client certificate.
So the normal flow of events that happens when browsing to this server is that the web browser (both Safari and Chrome) prompts the user to select a certificate and retry the operation.

So how can I accomplish this in a embedded WebView in a Cocoa project?
I have so far identified that the error is raised in the didFailProvisionalLoadWithError method:

- (void)webView:(WebView *)sender didFailProvisionalLoadWithError:(NSError *)error forFrame:(WebFrame *)frame {
    NSLog(@"webView:didFailProvisionalLoadWithError:forFrame:");
    NSLog(@"    error = %@", error);
}

The error is indeed error = Error Domain=NSURLErrorDomain Code=-1206 UserInfo=0x1006a8030 "The server “myserver.xxx” requires a client certificate.
But how can I display a dialog so that the user can select a certificate from the keychain?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

窗影残 2024-11-08 09:15:22

问题解决了。

WebView 组件的一个(已知)问题是罪魁祸首。
向 Apple 提交了 DTS 支持票并找到了解决方法。

编辑:
这是 DTS 的解决方法(我不知道这是否仍然有效,因为那是 3 年前的事了):

Magnus 好的,我有机会看看这个,我知道发生了什么
在。在我们开始讨论 WebView 之前,我需要先介绍一下
NSURLConnection 使用的委托方法的速度,这是
用于实际从网络加载数据的底层 API。
NSURLConnection 开始支持单一身份验证
委托回调,-connection:didReceiveAuthenticationChallenge:, 到
它通过了各种身份验证挑战
当时支持(用户名/密码式挑战)。在苹果电脑中
OS X 10.6(和 iOS 3.0)NSURLConnection 得到增强,支持两种
TLS 连接的其他身份验证质询类型: o
客户身份挑战
(NSURLAuthenticationMethodClientCertificate),为委托提供
为给定 TLS 连接选择客户端身份的机会
服务器信任挑战(NSURLAuthenticationMethodServerTrust),给出
委托人有机会覆盖服务器信任评估
对于给定的 TLS 连接 出于兼容性原因,它不是
可以将这些挑战传递给所有代表
情况下,所以 NSURLConnection 引入了一个新的委托回调,
-connection:canAuthenticateAgainstProtectionSpace:,允许委托人选择接受这些挑战。
* * * 现在,让我们将其带回到您的应用程序中。正如我提到的,WebView 使用
NSURLConnection,对于每个连接,充当连接
代表。它拦截身份验证挑战并将其传递给
它的资源加载委托。这对于老派来说效果很好
身份验证质询,因为 WebView 无需获取质询
必须做一些特别的事情;但 TLS 连接失败
身份验证挑战,因为代表必须选择这些
挑战。您真正需要的是 WebView 版本
“canAuthenticateAgainstProtectionSpace”身份验证质询。
嗯,事实证明,这个确实是实现了。在寻找
通过开源的WebView,我发现有一个私有的
委托回调,
-webView:资源:canAuthenticateAgainstProtectionSpace:forDataSource:,
这正是你想要的。
http://www.opensource.apple .com/source/WebKit/WebKit-7533.20.25/mac/WebView/WebResourceLoadDelegatePrivate.h
如果您实现该方法,您可以选择使用客户端身份
身份验证质询,并根据该质询向用户呈现
允许用户选择身份的界面。我制作了原型
这在您的测试应用程序中非常有用。这是我以前使用的代码
获取客户身份挑战:
- (BOOL)webView:(WebView *)发件人资源:(id)标识符 canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace
*)DataSource 的保护空间:(WebDataSource *)dataSource {
NSLog(@"%@", [保护空间认证方法]);
return [[protectionSpaceauthenticationMethod]isEqual:NSURLAuthenticationMethodClientCertificate];这是
我用来回复它的代码:
- (void)webView:(WebView *)发送者资源:(id)标识符 didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge
*)来自DataSource的挑战:(WebDataSource *)dataSource {
NSLog(@"didReceiveAuthenticationChallenge");
NSString *authenticationMethod = [[挑战保护空间]authenticationMethod];
NSLog(@" 身份验证方法 = %@", 身份验证方法);

 [[挑战发送者] continueWithoutCredentialForAuthenticationChallenge:challenge]; }

显然,在真正的应用程序中,您需要显示一些 UI,然后,一次
用户已选择客户端身份,为其创建凭证
(+[NSURLCredential credentialWithIdentity:证书:持久性:])
然后将该凭证应用于挑战
(-useCredential:forAuthenticationChallenge:)。
* * * 那么您从这里开始往哪里走呢?无论你做什么,你都应该
针对 WebView 提交错误以获取
-webView:resource:canAuthenticateAgainstProtectionSpace:forDataSource:在公共标头中发布的委托回调。这是一个显而易见的、
最烦人的是遗漏。 http://developer.apple.com/bugreporter/
提交错误后,请将错误编号发送给我,以便我可以
与这起事件联系起来。除此之外,前进的道路就更少了
清除。如果您要创建非 Mac App Store 应用程序,我的建议
就是你只需实施
正如我所见,“canAuthenticateAgainstProtectionSpace”委托回调
如上所示并继续你的生活。 OTOH,如果你正在创建一个
Mac App Store应用程序,其中使用私有API,包括委托
回调是严格禁止的,生活会变得更加棘手。让我
了解这种情况,我们可以讨论您的选择。分享并享受

Problem solved.

A (known) issue with the WebView component was the culprit.
Opened up a DTS support ticket with Apple and got a workaround.

EDIT:
Here's the workaround from DTS ( I have no idea if this is still valid, since it was 3 years ago):

Magnus OK, I've had a chance to look at this and I know what's going
on. Before we start talking about WebView, I need to bring you up to
speed on the delegate methods used by NSURLConnection, which is the
underlying API used to actually load data off the 'net.
NSURLConnection started out supporting a single authentication
delegate callback, -connection:didReceiveAuthenticationChallenge:, to
which it passed the various authentication challenges that were
supported at that time (username/password-style challenges). In Mac
OS X 10.6 (and iOS 3.0) NSURLConnection was enhanced to support two
addition types of authentication challenges for TLS connections: o
client identity challenges
(NSURLAuthenticationMethodClientCertificate), giving the delegate the
opportunity to select a client identity for a given TLS connection o
server trust challenges (NSURLAuthenticationMethodServerTrust), giving
the delegate the opportunity to override the server trust evaluation
for a given TLS connection For compatibility reasons it was not
possible to pass these challenges to the delegate under all
circumstances, so NSURLConnection introduced a new delegate callback,
-connection:canAuthenticateAgainstProtectionSpace:, that allows the delegate to opt in to these challenges.
* * * Now, let's bring this back to your app. As I mentioned, WebView uses
NSURLConnection and, for each connection, acts as the connection
delegate. It intercepts authentication challenges and passes them to
its resource load delegate. This works just fine for old school
authentication challenges, because WebView gets the challenge without
having to do anything special; but it fails for TLS connection
authentication challenges, because the delegate has to opt in those
challenges. What you really need is the WebView version of the
'canAuthenticateAgainstProtectionSpace' authentication challenge.
Well, it turns out that this is actually implemented. In looking
through the open source for WebView, I found that there's a private
delegate callback,
-webView:resource:canAuthenticateAgainstProtectionSpace:forDataSource:,
that does exactly what you want.
http://www.opensource.apple.com/source/WebKit/WebKit-7533.20.25/mac/WebView/WebResourceLoadDelegatePrivate.h
If you implement that method you can opt in to the client identity
authentication challenge and, based on that challenge, present a user
interface that allows the user to select an identity. I prototyped
this in your test app and it works a charm. Here's the code I used to
get the client identity challenge:
- (BOOL)webView:(WebView *)sender resource:(id)identifier canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace
*)protectionSpace forDataSource:(WebDataSource *)dataSource {
NSLog(@"%@", [protectionSpace authenticationMethod]);
return [[protectionSpace authenticationMethod] isEqual:NSURLAuthenticationMethodClientCertificate]; } and here's the
code I used to respond to it:
- (void)webView:(WebView *)sender resource:(id)identifier didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge
*)challenge fromDataSource:(WebDataSource *)dataSource {
NSLog(@"didReceiveAuthenticationChallenge");
NSString *authenticationMethod = [[challenge protectionSpace] authenticationMethod];
NSLog(@" authenticationMethod = %@", authenticationMethod);

 [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge]; }

Obviously in a real app you'd need to display some UI and then, once
the user has selected a client identity, create a credential for it
(+[NSURLCredential credentialWithIdentity:certificates:persistence:])
and then apply that credential to the chalenge
(-useCredential:forAuthenticationChallenge:).
* * * So where do you proceed from here? Regardless of what else you do, you should
file a bug against WebView to get the
-webView:resource:canAuthenticateAgainstProtectionSpace:forDataSource: delegate callback published in the public headers. It's an obvious,
and most annoying, omission. http://developer.apple.com/bugreporter/
Once you've filed a bug, please send me the bug number so that I can
associate it with this incident. Beyond that, the way forward is less
clear. If you're creating a non-Mac App Store app, my recommendation
would be that you just implement the
'canAuthenticateAgainstProtectionSpace' delegate callback as I've
shown above and move on with your life. OTOH, if you're creating a
Mac App Store app, where the use of private API, including delegate
callbacks, is strictly prohibited, life gets a lot trickier. Let me
know in that case and we can discuss your options. Share and Enjoy

橘亓 2024-11-08 09:15:22

设置 WebResourceLoadDelegate 并实现身份验证挑战相关的委托方法。收到身份验证质询时,系统会提示您,此时您可以提供要使用的证书。

预计到达时间:以下是如何从存储在 clientSide.p12 中的证书创建 NSURLCredential

NSString *thePath = [[NSBundle mainBundle]
        pathForResource:@"clientside" ofType:@"p12"];
NSData *PKCS12Data = [[NSData alloc] initWithContentsOfFile:thePath];
CFDataRef inPKCS12Data = (CFDataRef)PKCS12Data;    
SecIdentityRef identity;
SecTrustRef trust;
extractIdentityAndTrust(inPKCS12Data, &identity, &trust);

SecCertificateRef certificate = NULL;
SecIdentityCopyCertificate (identity, &certificate); 

const void *certs[] = {certificate};
CFArrayRef certArray = CFArrayCreate(kCFAllocatorDefault, certs, 1, NULL);

NSURLCredential *credential = [NSURLCredential
        credentialWithIdentity:identity
        certificates:(NSArray*)certArray
        persistence:NSURLCredentialPersistencePermanent];

这来自另一个问题。您可能还会发现这个问题很有帮助。我通过谷歌搜索“nsurlcredential 证书”找到了这些。

Set a WebResourceLoadDelegate and implement the authentication-challenge–related delegate methods. You will be prompted when an authentication challenge is received, at which time you can provide the certificate to use.

ETA: Here is how you can create an NSURLCredential from a certificate stored in clientSide.p12:

NSString *thePath = [[NSBundle mainBundle]
        pathForResource:@"clientside" ofType:@"p12"];
NSData *PKCS12Data = [[NSData alloc] initWithContentsOfFile:thePath];
CFDataRef inPKCS12Data = (CFDataRef)PKCS12Data;    
SecIdentityRef identity;
SecTrustRef trust;
extractIdentityAndTrust(inPKCS12Data, &identity, &trust);

SecCertificateRef certificate = NULL;
SecIdentityCopyCertificate (identity, &certificate); 

const void *certs[] = {certificate};
CFArrayRef certArray = CFArrayCreate(kCFAllocatorDefault, certs, 1, NULL);

NSURLCredential *credential = [NSURLCredential
        credentialWithIdentity:identity
        certificates:(NSArray*)certArray
        persistence:NSURLCredentialPersistencePermanent];

This comes from another question. You might also find this question helpful. I found these by Googling for "nsurlcredential certificate".

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文