I have a sample iOS app in Xcode that I run in the iOS 17.5 Simulator. It creates a WKWebView and configures a proxy via the ProxyConfiguration API, it works as expected unless the proxy tries to establish mTLS. It seems there is no way to handle the client certificate request when using a proxy. If I navigate to a page that requests mTLS without a proxy configured, it works as expected. Here is a minimal repro:
#import "ViewController.h"
#import <WebKit/WebKit.h>
@import Foundation;
@import WebKit;
@interface ViewController () <WKNavigationDelegate>
@property (nonatomic,strong) WKWebView* webView;
@property (nonatomic, strong) WKWebViewConfiguration * webConfig;
@end
@implementation ViewController
- (void)loadView {
[super loadView];
nw_protocol_options_t tls_options = nw_tls_create_options();
sec_protocol_options_t sec_options = nw_tls_copy_sec_protocol_options(tls_options);
sec_protocol_options_set_challenge_block(
sec_options,
^(sec_protocol_metadata_t metadata, sec_protocol_challenge_complete_t challenge_complete) {
NSLog(@"Inside of challenge block");
challenge_complete(nil);
},
dispatch_get_main_queue());
nw_endpoint_t proxy_endpoint =
nw_endpoint_create_host(GetHost(), GetPort());
nw_relay_hop_t relay =
nw_relay_hop_create(nil, proxy_endpoint, tls_options);
nw_proxy_config_t proxy_config =
nw_proxy_config_create_relay(relay, nil);
nw_proxy_config_add_match_domain(proxy_config, "api.ipify.org");
self.webConfig = [[WKWebViewConfiguration alloc] init];
self.webConfig.websiteDataStore = [WKWebsiteDataStore nonPersistentDataStore];
self.webConfig.websiteDataStore.proxyConfigurations = @[ proxy_config ];
self.webView = [[WKWebView alloc] initWithFrame:self.view.frame configuration:self.webConfig];
self.webView.navigationDelegate = self;
[self.view addSubview:self.webView];
}
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"%s",__func__);
NSURL* url = [[NSURL alloc] initWithString:@"https://api.ipify.org"];
NSURLRequest* request = [[NSURLRequest alloc] initWithURL:url];
[self.webView loadRequest:request];
}
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
NSLog(@"%s",__func__);
}
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error {
NSLog(@"%s. Error %@",__func__,error);
}
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler {
NSLog(@"%s",__func__);
NSLog(@"protection space: %@", challenge.protectionSpace.authenticationMethod);
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
}
@end
The logs for this code show:
-[ViewController viewDidLoad]
-[ViewController webView:didStartProvisionalNavigation:]
-[ViewController webView:didFailProvisionalNavigation:withError:]. Error Error Domain=NSURLErrorDomain Code=-1206 "The server “api.ipify.org” requires a client certificate."
If we don't set up the ProxyConfiguration and navigate to a site that requires mTLS, the logs look like this:
-[ViewController viewDidLoad]
-[ViewController webView:didReceiveAuthenticationChallenge:completionHandler:]
protection space: NSURLAuthenticationMethodServerTrust
-[ViewController webView:didReceiveAuthenticationChallenge:completionHandler:]
protection space: NSURLAuthenticationMethodClientCertificate
-[ViewController webView:didStartProvisionalNavigation:]
//...
Eventually the request fails but the key difference is that didReceiveAuthenticationChallenge
was invoked. When using the ProxyConfiguration neither that function nor the block we set via sec_protocol_options_set_challenge_block
were run.
I also tried to provide the client identity via sec_protocol_options_set_local_identity
to no avail, and I've tried configuring these options too but they had no effect
sec_protocol_options_add_tls_application_protocol(sec_options, "h2");
sec_protocol_options_set_max_tls_protocol_version(sec_options, tls_protocol_version_TLSv13);
sec_protocol_options_set_peer_authentication_required(sec_options, true);
Am I missing something? Or is this a bug in the ProxyConfiguration API?
I suspect that this is just a limitation of the Network framework proxy support. But before I sent you off to file a bug, I want to ask about this:
I have a sample iOS app in Xcode that I run in the iOS 17.5 Simulator.
Do you see the same behaviour when testing on a real device?
My guess is that you will, but I wanna be sure because the simulator uses a big chunk of the macOS networking stack. Thus, when faced with weird networking problems, it’s best to test on real hardware.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"