BuySellAds.com

My book Barcodes with iOS 7 is nearing completion. Buy it now to get early access!
Our DNA is written in Objective-C
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

%d bloggers like this: