UIWebView for browsing self-signed websites (no private api, not NSURLConnection) - is this possible?

This raises a ton of questions that ask this question: can I get a UIWebView to view a self-signed HTTPS website?

And the answers always include:

  • Use private api call for NSURLRequest : allowsAnyHTTPSCertificateForHost
  • Instead, use NSURLConnection and the canAuthenticateAgainstProtectionSpace delegate, etc.

This will not be done for me.
(1) - means that I can not successfully send to the application store.
(2) - the use of NSURLConnection means CSS, images, and other things that are not loaded to load from the server after receiving the initial HTML page.

Does anyone know how to use UIWebView to view a self-signed https page, please, which does not include the two methods above?

Or - if you use NSURLConnection you can actually use it to display a web page with CSS, images, and everything else - that would be great!

Cheers
Stretch.

+47
ios iphone uiwebview
Jul 20 2018-12-12T00:
source share
8 answers

Finally, I get it!

What you can do is:

Initiate your request using UIWebView as usual. Then - in webView:shouldStartLoadWithRequest - we answer NO and instead run NSURLConnection with the same request.

Using NSURLConnection , you can communicate with a self-signed-in server, since we can control authentication using additional delegate methods that are not available for UIWebView . Thus, using connection:didReceiveAuthenticationChallenge , we can authenticate against the self-signed server.

Then in connection:didReceiveData we will cancel the NSURLConnection request and run the same request again using UIWebView - which will work now because we have already passed the server authentication :)

Below are the relevant code snippets.

Note. The instance variables that you see are of the following type:
UIWebView *_web
NSURLConnection *_urlConnection
NSURLRequest *_request

(I use a var instance for _request , since in my case it is POST with a lot of login data, but you can change the use of the request passed as arguments to the methods if you need to.)

 #pragma mark - Webview delegate // Note: This method is particularly important. As the server is using a self signed certificate, // we cannot use just UIWebView - as it doesn't allow for using self-certs. Instead, we stop the // request in this method below, create an NSURLConnection (which can allow self-certs via the delegate methods // which UIWebView does not have), authenticate using NSURLConnection, then use another UIWebView to complete // the loading and viewing of the page. See connection:didReceiveAuthenticationChallenge to see how this works. - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType; { NSLog(@"Did start loading: %@ auth:%d", [[request URL] absoluteString], _authenticated); if (!_authenticated) { _authenticated = NO; _urlConnection = [[NSURLConnection alloc] initWithRequest:_request delegate:self]; [_urlConnection start]; return NO; } return YES; } #pragma mark - NURLConnection delegate - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge; { NSLog(@"WebController Got auth challange via NSURLConnection"); if ([challenge previousFailureCount] == 0) { _authenticated = YES; NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; [challenge.sender useCredential:credential forAuthenticationChallenge:challenge]; } else { [[challenge sender] cancelAuthenticationChallenge:challenge]; } } - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response; { NSLog(@"WebController received response via NSURLConnection"); // remake a webview call now that authentication has passed ok. _authenticated = YES; [_web loadRequest:_request]; // Cancel the URL connection otherwise we double up (webview + url connection, same url = no good!) [_urlConnection cancel]; } // We use this method is to accept an untrusted site which unfortunately we need to do, as our PVM servers are self signed. - (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace { return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]; } 

I hope this helps others with the same problem as me!

+72
Jul 26 2018-12-12T00:
source share
β€” -

A reliable answer seems like a great workaround, but it uses legacy APIs. So, I thought it might be useful to update the code.

In this code example, I added routines to the ViewController that contains my UIWebView. I have done the UIViewController UIWebViewDelegate and NSURLConnectionDataDelegate. Then I added 2 data items: _Authenticated and _FailedRequest. The code is as follows:

 -(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { BOOL result = _Authenticated; if (!_Authenticated) { _FailedRequest = request; [[NSURLConnection alloc] initWithRequest:request delegate:self]; } return result; } -(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { NSURL* baseURL = [_FailedRequest URL]; if ([challenge.protectionSpace.host isEqualToString:baseURL.host]) { NSLog(@"trusting connection to host %@", challenge.protectionSpace.host); [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge]; } else NSLog(@"Not trusting connection to host %@", challenge.protectionSpace.host); } [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge]; } -(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)pResponse { _Authenticated = YES; [connection cancel]; [_WebView loadRequest:_FailedRequest]; } 

I set _Authenticated NO when I load the view and do not reset it. This seems to allow UIWebView to execute multiple requests on the same site. I did not try to switch sites and try to return. This may result in the need to reset _Authenticated. Also, if you switch sites, you should keep a dictionary (one entry for each host) for _Authenticated, not BOOL.

+63
Feb 25 '13 at 18:59
source share

This is a panacea!




 BOOL _Authenticated; NSURLRequest *_FailedRequest; #pragma UIWebViewDelegate -(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { BOOL result = _Authenticated; if (!_Authenticated) { _FailedRequest = request; NSURLConnection *urlConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self]; [urlConnection start]; } return result; } #pragma NSURLConnectionDelegate -(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { NSURL* baseURL = [NSURL URLWithString:@"your url"]; if ([challenge.protectionSpace.host isEqualToString:baseURL.host]) { NSLog(@"trusting connection to host %@", challenge.protectionSpace.host); [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge]; } else NSLog(@"Not trusting connection to host %@", challenge.protectionSpace.host); } [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge]; } -(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)pResponse { _Authenticated = YES; [connection cancel]; [self.webView loadRequest:_FailedRequest]; } - (void)viewDidLoad{ [super viewDidLoad]; NSURL *url = [NSURL URLWithString:@"your url"]; NSURLRequest *requestURL = [NSURLRequest requestWithURL:url]; [self.webView loadRequest:requestURL]; // Do any additional setup after loading the view. } 
+15
Feb 12 '14 at 14:28
source share

If you want to access a closed server with a self-signed certificate for testing only, you do not need to write code. You can manually do a system-wide certificate import.

To do this, you need to download the server certificate using mobile safari, which then requests the import.

This can be used in the following circumstances:

  • the number of test devices is small.
  • you trust the server certificate

If you don’t have access to the server certificate, you can return to the following method to extract it from any HTTPS server (at least on Linux / Mac, windows guys will have to download the OpenSSL binaries somewhere):

 echo "" | openssl s_client -connect $server:$port -prexit 2>/dev/null | sed -n -e '/BEGIN\ CERTIFICATE/,/END\ CERTIFICATE/ p' >server.pem 

Please note that depending on the version of OpenSSL, the certificate may be doubled in the file, so it’s best to take a look at it with a text editor. Put the file somewhere on the network or use

python -m SimpleHTTPServer 8000

a shortcut to access it from your mobile safari at http: // $ your_device_ip: 8000 / server.pem.

+7
Oct 18 '14 at 10:14
source share

This is a smart workaround. However, perhaps the best (albeit more intensively using the code) solution would be to use NSURLProtocol, as shown in the Apple CustomHTTPProtocol code example. From README:

"CustomHTTPProtocol shows how to use the NSURLProtocol subclass to intercept NSURLC connections created by a high-level subsystem that would not otherwise disclose its network connections. In this particular case, it intercepts HTTPS requests made by the web view and redefines the trust server, allowing you to browse a site whose certificate is not trusted by default. "

Check out the full example: https://developer.apple.com/library/ios/samplecode/CustomHTTPProtocol/Introduction/Intro.html

+3
Dec 02 '13 at 19:07
source share

This is a fast compatible equivalent that works for me. I have not converted this code to use NSURLSession instead of NSURLConnection , and I suspect that it will add a lot of complexity to understand it.

 var authRequest : NSURLRequest? = nil var authenticated = false var trustedDomains = [:] // set up as necessary func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool { if !authenticated { authRequest = request let urlConnection: NSURLConnection = NSURLConnection(request: request, delegate: self)! urlConnection.start() return false } else if isWebContent(request.URL!) { // write your method for this return true } return processData(request) // write your method for this } func connection(connection: NSURLConnection, willSendRequestForAuthenticationChallenge challenge: NSURLAuthenticationChallenge) { if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust { let challengeHost = challenge.protectionSpace.host if let _ = trustedDomains[challengeHost] { challenge.sender!.useCredential(NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!), forAuthenticationChallenge: challenge) } } challenge.sender!.continueWithoutCredentialForAuthenticationChallenge(challenge) } func connection(connection: NSURLConnection, didReceiveResponse response: NSURLResponse) { authenticated = true connection.cancel() webview!.loadRequest(authRequest!) } 
+3
Oct 08 '15 at 1:18
source share

Here is the working code of Swift 2.0

 var authRequest : NSURLRequest? = nil var authenticated = false func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool { if !authenticated { authRequest = request let urlConnection: NSURLConnection = NSURLConnection(request: request, delegate: self)! urlConnection.start() return false } return true } func connection(connection: NSURLConnection, didReceiveResponse response: NSURLResponse) { authenticated = true connection.cancel() webView!.loadRequest(authRequest!) } func connection(connection: NSURLConnection, willSendRequestForAuthenticationChallenge challenge: NSURLAuthenticationChallenge) { let host = "www.example.com" if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust && challenge.protectionSpace.host == host { let credential = NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!) challenge.sender!.useCredential(credential, forAuthenticationChallenge: challenge) } else { challenge.sender!.performDefaultHandlingForAuthenticationChallenge!(challenge) } } 
+1
Jan 07 '16 at 7:27
source share

To build the @spirographer response , I put something together to use Swift 2.0 with NSURLSession . However, this does NOT work yet. See below.

 func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool { let result = _Authenticated if !result { let sessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration() let session = NSURLSession(configuration: sessionConfiguration, delegate: self, delegateQueue: NSOperationQueue.mainQueue()) let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in if error == nil { if (!self._Authenticated) { self._Authenticated = true; let pageData = NSString(data: data!, encoding: NSUTF8StringEncoding) self.webView.loadHTMLString(pageData as! String, baseURL: request.URL!) } else { self.webView.loadRequest(request) } } } task.resume() return false } return result } func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) { completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential, NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!)) } 

I will return the original HTML response, so the page displays plain HTML, but no CSS styles are applied to it (it looks like the CSS request is rejected). I see a bunch of these errors:

 NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813) 

It seems that any request made using webView.loadRequest is not executed in the session, so the connection is rejected. I have Allow Arbitrary Loads in Info.plist . What confuses me is why the NSURLConnection will work (apparently the same idea), but not the NSURLSession .

0
May 31 '16 at 16:36
source share



All Articles