Ad

Our DNA is written in Swift
Jump

Ignoring Certificate Errors on NSUrlRequest

Fabian asks:

I’m trying to request data from a website via HTTPS that does not have valid certificate. How can I ignore the certificate error?

When the iPhone makes a HTTPS request it verifies that the certificate used to encrypt the data has a valid root certificate authority. Usually – for big sites – this is provided by Thawte or Verisign or any other recognized Root Certification Authority (CA). A bundle of the public certificates of such CAs is installed in the OS and enables the client to know which CAs are valid.

The problem arises however if you don’t have the funds to purchase such a certificate from a CA, those are expensive. Or sometimes you want to create a certificate for your own use or testing. This is called self-signed certificates. Those are also deemed invalid at first glance, unless you tell your browser to accept these certificates. Or it may be the case of Twitter who seem to have an expired certificate on one of their API servers.

When I googled for a solution it appears that Apple left out the possibility to ignore invalid certificates. At least from public documentation. The reason for this can only be speculated on. Most likely because Apple deems such practices unsafe. You might build a vector for a security attack into your code if you where able to. So they rather not tell you about it.

If you do a simple synchronous URL request like below you will get no data but instead an NSError “untrusted server certificate”.

NSString *url = @"https://www.drobnik.com:8443/";  // server name does not match
NSURL *URL = [NSURL URLWithString:url];
 
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLResponse *response;
NSError *error = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:request
				returningResponse:&response error:&error];
 
if (error)
{
	NSLog(@"%@", [error localizedDescription]);
}
else
{
	NSString *result = [[[NSString alloc] initWithData:data
					encoding:NSUTF8StringEncoding] autorelease];
	NSLog(@"%@", result);
}

If there where no Google then you would resign here, but fortunately some API diggers have uncovered not one but two methods how to go about ignoring invalid certificates. Both revolve around an undocumented class method of NSURLRequest: setAllowsAnyHTTPSCertificate. The first method written down by Alexandre Colucci defines a dummy category interface to eliminate the warning that the object might not respond to this message.

Above the @implementation you define the dummy interface:

@interface NSURLRequest (DummyInterface)
+ (BOOL)allowsAnyHTTPSCertificateForHost:(NSString*)host;
+ (void)setAllowsAnyHTTPSCertificate:(BOOL)allow forHost:(NSString*)host;
@end

And before you make the call you invoke the private class method, in our example right before the sendSynchronousRequest.

[NSURLRequest setAllowsAnyHTTPSCertificate:YES forHost:[URL host]];

This will cause the ignoring we are looking for and you no longer get a certificate warning. The second method, first mentioned by somebody named Deklann on a since discontinued site, is slightly more elegant because it eliminates the need to add the mentioned dummy interface to every class where you want to ignore SSL errors. Instead it overrides the private method with one that always responds YES to the question if any HTTPS Certificate would be allowed.

NSURLRequest+IgnoreSSL.h

#import
 
@interface NSURLRequest (IgnoreSSL)
 
+ (BOOL)allowsAnyHTTPSCertificateForHost:(NSString*)host;
 
@end

NSURLRequest+IgnoreSSL.m

#import "NSURLRequest+IgnoreSSL.h"
 
@implementation NSURLRequest (IgnoreSSL)
+ (BOOL)allowsAnyHTTPSCertificateForHost:(NSString*)host
{
+ (BOOL)allowsAnyHTTPSCertificateForHost:(NSString*)host
{
	// ignore certificate errors only for this domain
	if ([host hasSuffix:@"drobnik.com"])
	{
		return YES;
	}
	else
	{
		return NO;
	}
}
 
@end

This replacement method gets automatically called and you can decide based on the host to allow any certificates or not. Alternatively you can always return YES regardless of the host parameter to ignore all invalid certificates.

In my testing I found that I actually didn’t have to add the #import for the header at the top of any file. Still the replacement method gets called. Weird, I thought categories would only take effect if you add the header, but this is one example where the simple presence of the .m in the project seems to be enough for it to work.

One question remains: Will anyone get into trouble with Apple because he is using forbidden methods? Well, Apple does not like if you use private frameworks specifically. I know of no instance where somebody got bounced because of this approach. Technically neither constitutes use of a private framework because NSURLRequest is a public framework, so public in fact that it’s part of WebKit, which is open source if I am not mistaken.

This falls into the category of things you can do, but that Apple generally discourages because it might break in a future version of the SDK. These are things like walking through the view stack of a WebView and picking out specific views that you have no business of touching. But I think that use of this method is probably reasonably safe because it’s been in there for at least 6 years, judging from trac tickets on Webkit.org.

What will probably happen is that Apple listens to feature requests on Radar who are asking for this to be made public, because it’s simply a nuisance to not have this feature.

Update: I have received a report that somebody got his app rejected for using this method. So you can really only use this for testing.


Categories: Q&A

14 Comments »

  1. Oliver, I’ve been reading your blog for a long time now, and found it very useful. I thing bothers me though. You frequently post very useful and free to use code snippets and categories, but you don’t provide download links for the source code. I know I’m just a whiney bitch, but I keep copy+pasting from your blog every day into my code repo, and a download link instead would be just so nice.

  2. Hi Marton,

    for all the code that I am posting I am usually creating a new project and then put all that I am interested in into the app delegate. Then I’m looking at the output of NSLog statements. If it is free of errors and has the promised result then I copy/paste all relevant parts into pre sections to get the syntax highlighting.

    So I have two problems with having download links: 1) there is no more to see that I am already showing, the rest is just a standard project. 2) if you where to take one of my sample projects and would have to go looking for which parts you need you would take more time reusing my code than by having the essentials before you and copy/pasting from there. 3) when I’m creating code that too complex for a blog post then I’m saving it for my “parts store” or MyAppSales.

    Finally most of the time I’ll be using code that I thus discovered in MyAppSales and other projects like Kernseife. So if I propose category classes, most likely you can find them in one of these projects anyway. But your point is taken, if I do that the next time, I will mention from which public project you can copy the files.

  3. There is a supported API for ignoring bad certificates during NSURLConnection loads. To do so, simply add something like this to your NSURLConnection delegate:

    - (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
      return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
    }
     
    - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
      if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
        if ([trustedHosts containsObject:challenge.protectionSpace.host])
          [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
     
      [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
    }

    Note that connection:didReceiveAuthenticationChallenge: can send its message to challenge.sender (much) later, say from a delegate method of an SFCertificateTrustPanel.

  4. since apple do not allow private api calls, ghenriksen’s way works great for me, Thanks!

  5. But the drops method is also working fine. I assume that is much more easier for newbies like me to absorb( Somebody might like to elaborate on this). Anyways thanks a lot for the help.

  6. Thanks for the blog.
    I have a question.

    If i use the ‘sendSynchronousRequest’ api call, and if the certificate is trusted, will this API work?

    The reason i ask is we already have an app which uses ‘sendSynchronousRequest’ and a customer is having issues with https.
    What kind of certificate should the customer use? Which company?
    Ultimately what certificate would enable this API – sendSynchronousRequest to work?

  7. synchronous calls don’t use the delegate, so it won’t work. The only way to get HTTPS work with synchronous calls is to have a valid root certificate, either because the server certificate comes from a regular provider or because you have installed your own ROOTCA on the device via Apple’s management tool.

  8. Hi,
    What is trustedhosts in your code.what it means to?

  9. I was pulling my hair out all day yesterday because of this issue. I couldn’t do any testing on my app. Spent a lot of time on the internet trying various methods but none worked. The first method works like a charm (didn’t try the second one). Thank you very much.

  10. Very nice solution – thanks. As you say, not something you’d submit to the App Store, but perfect for testing.

  11. Hello, I ‘m using a JSON framwork which using a simple NSURL . I have an error NSURLConnection/CFURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813)
    This solution will work for me ? Because it is nsurlrequest ?