且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

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

更新时间:2022-10-25 09:16:12

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