iOS Untrusted Server Certificate

Had a problem recently with iOS 3.0 where it did not trust my server’s SSL certificate.

Newer versions do, so iOS 3.0 must simply not have the root certificates.

There are two ways to solve this: one is to blindly accept all certificates. That’s bad – as you lose the identify protection that SSL provides (and subject your users to man-in-the-middle attacks). It’s also harder, as you have to apply this to *every* connection (and if you use a UIWebView that may not be possible). But if you really want, the info is here.

The better approach is to include the root certificates of the certificate authority (no need for your actual certificate) in your App. This just makes the iPhone trust your CA, and will even work if you re-issue the cert in the future with the same CA (no need to update the app).

To trust a certificate, simply add this method:

// ref: http://stackoverflow.com/questions/1746005/importing-an-ssl-cert-under-the-iphone-sdk
+ (void) trustCert:(NSString*)filename ext:(NSString*)ext
{
	// NB. certificate must be in DER format (SecCertificateCreateWithData will return nil if not)
	// To convert, on the command line type: openssl x509 -in your_cert_name.cer -out UTN-your_cert_name.der -outform DER
	
	OSStatus            err;
	NSString *          path;
	NSData *            data;
	SecCertificateRef   cert;
	
	path = [[NSBundle mainBundle] pathForResource:filename ofType:ext];
	assert(path != nil);
	
	data = [NSData dataWithContentsOfFile:path];
	assert(data != nil);
	
	cert = SecCertificateCreateWithData(NULL, (CFDataRef) data);
	assert(cert != NULL);
	
	err = SecItemAdd(
					 (CFDictionaryRef) [NSDictionary dictionaryWithObjectsAndKeys:
										(id) kSecClassCertificate,  kSecClass, 
										cert,                       kSecValueRef, 
										nil
										], 
					 NULL
					 );
	assert(err == noErr || err == errSecDuplicateItem);
	
	CFRelease(cert);	
}

I got that method from here, but added a check to see if the cert was already imported (and not assert on that case).

If you don’t have your certificates on hand, you will need to export all the certificates in the chain of trust. I did this from Firefox using the view certificate -> export feature. Export all certs in the tree, except your server’s one itself (this one is not needed). For me, this meant 3 certificates.

Make sure your cert is in DER format. You can convert the typical ASCII
CER format (such as the ones exported through Firefox) into DER in this way on the command line:

openssl x509 -in your_cert_name.cer -out UTN-your_cert_name.der -outform DER

Add your .der file to your project (all targets).

Then, just call this line before you use the cert:

[YourClass trustCert:@"your_cert_name" ext:@"der"];

For me, I had to trust all 3 certificates in the chain. The only cert I didn’t need to bundle was the one for my server itself.

WARNING: due to the way this code works, you only have to call it *once* and it keeps the cert in your app’s keychain (I think). Even if you delete the app, the cert remains there. So take care when testing your code. I think if you change the bundle identifier for your app, it would be like a fresh install. Remember, deleting the app will not delete the cert from that app’s keychain, so this can fool you into thinking the code is no longer needed (or something).

Importantly – this code only solves your cert issues from WITHIN your App. To solve them from Mobile Safari (these are two, mutually exclusive solutions), you need to create a mobile config file containing all your root certificates, and get the user to install it. You can do that with the iPhone Configuration Utility, create the config and send users to that URL on their iPhones.


Comments are closed.