In this article, you will find out how you can create your very own free trusted SSL certificate for development purposes.

Firstly, it's important to understand what will happen if you don't have a trusted certificate.

On the web, your users will always be confronted with the following warning whenever they access your site:

In a production environment, this will scare off users and cost you traffic and reputation. In a development environment, it's annoying, and also if this is happening on your API endpoints, it could cause trouble downstream, for example on iOS.

On an iOS device, the connection will simply fail. You can, however, circumvent this by implementing the following delegate methods:

- (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 ([challenge.protectionSpace.host isEqualToString:@"blog.andydrizen.co.uk"])
        {
            NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
            [challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
        }
    }

    [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}

The above will ensure that any SSL certificate for my domain is trusted. This will allow the connection to succeed, but at what cost? I've now told my App to blindly accept any SSL certificate that relates to my domain - this leaves me wide open to Man-In-The-Middle (MITM) attacks! When creating an SSL certificate, you can specify it for any domain that you wish; the whole point of the CA is to vouch for the certificate, which this technique sidesteps! You might think that this is OK in a development environment, but I'd argue that it isn't for the following reasons.

  1. Developers often forget to omit this hack for production code. Nick Arnott wrote an eye-opening blog article on exactly this.
  2. Tools like Charles proxy will be able to view your unencrypted traffic; this might seem benign, or even useful because it allows for debugging. However, I'd argue that it's more useful to demonstrate that the SSL is correctly configured to accept only trusted certificates, and as for debugging, remember Charles can be equipped with your trusted cert anyway.
  3. It will force your NSURLConnections to be asynchronous, as it requires the delegate interaction to confirm the server trust.

Option 1: Buy One (Production or Development environment)

The easiest way to get a trusted SSL certificate is to purchase one from a trusted Certificate Authority (CA) such as GeoTrust or Symantec (previously Verisign). The downside to this is the initial cost (usually $50 - $400). Some companies are cheaper, and some offer free trials, so this might meet your needs - if it does, go for it. For iOS, your only consideration needs to be that the issuer is trusted by Apple. Each iOS device has a sqlite database (located at Security.framework/TrustStore.sqlite3) of trusted root CAs; you can see the list at KB5012.

Note: For production environments, this is your only choice.

Option 2: Make One (Development environment only)

It's dead easy to create your own CA and necessary certificates. The issue is that nobody trusts you, and rightly so.

If everybody is trusted equally, then trust becomes a worthless commodity. When Google created their SSL certificate with Versign, they can be certain that no other trusted CA will create a conflicting SSL certificate. This means that whenever you see a trusted certificate for google.com, you can be sure that your login details aren't subject to any MITM attack. If the CA that I created were trusted, (and remember I can create an SSL certificate for any domain) I could create a google.com certificate and obtain your username and password.

So for this to be useful to us, we need to instruct our browser or iOS device to trust our authority. For the browser, we could just check that handy "Always trust these certificates" box when the error appears, as shown here:

But it would be a lot nicer if we didn't need to do this. If the operating system knew about our home-made CA, it would have been approved automatically.

On a Mac It turns out that this is easy to do - once we've made the CA (see below) we simply double click on it and install it to the Keychain - job done!

And what about iOS? As we mentioned earlier, iOS devices have a database of trusted CAs that we can't directly modify. However, it is possible to add a CA by emailing the certificate to yourself, and then opening it on the device - this will allow you to install the CA in the Profiles section of your Settings.app - do this for every device that will be testing your app in development. Note: do not open the certificate in Safari and tap "Accept" - all this does is suppress the warning in future, rather than trust the CA (h/t HttpWatch). Also, if you have physical access to the device, you can also use the iPhone Configuration Utility tool, which you might find easier than doing the email dance.

How to Create the CA, SSL Certificate and Client Certificates

To actually create the self-signed certificate, edit the following script to meet your needs, and then run it.

# NOTE: common name must match address of server.

COUNTRY="GB"
STATE="London"
LOCATION="London"
COMMON_NAME="localhost"
VALIDITY=365
P12_NAME="Andy Drizen"

FILEPATH="ssl"

# Create a directory to install these certs
mkdir -p $FILEPATH

# Create the CA Certificate
openssl genrsa -out "$FILEPATH/ca.key" 2048
openssl req -new -x509 -days $VALIDITY -key "$FILEPATH/ca.key" -out "$FILEPATH/ca.crt" -subj "/C=$COUNTRY/ST=$STATE/L=$LOCATION/CN=$COMMON_NAME"

# Create the Server Certificate
openssl genrsa -out "$FILEPATH/server.key" 1024
openssl req -new -key "$FILEPATH/server.key" -out "$FILEPATH/server.csr" -subj "/C=$COUNTRY/ST=$STATE/L=$LOCATION/CN=$COMMON_NAME"
openssl x509 -req -in "$FILEPATH/server.csr" -out "$FILEPATH/server.crt" -CA "$FILEPATH/ca.crt" -CAkey "$FILEPATH/ca.key" -CAcreateserial -days $VALIDITY

# Create the Client Certificate
openssl genrsa -out "$FILEPATH/client.key" 1024
openssl req -new -key "$FILEPATH/client.key" -out "$FILEPATH/client.csr" -subj "/C=$COUNTRY/ST=$STATE/L=$LOCATION/CN=$COMMON_NAME"
openssl x509 -req -in "$FILEPATH/client.csr" -out "$FILEPATH/client.crt" -CA "$FILEPATH/ca.crt" -CAkey "$FILEPATH/ca.key" -CAcreateserial -days $VALIDITY

# Convert client certificate to p12
openssl pkcs12 -export -in "$FILEPATH/client.crt" -inkey "$FILEPATH/client.key" -name "$P12_NAME" -out "$FILEPATH/client.p12"

All that remains is for you to install the CA on any device that you wish to develop on (in order to trust the certificate). If needed, you can install the client certificate (the client.crt and client.p12).

A Node.JS Example

To use the new certificate, specify the server.key, server.crt and ca.crt in your web server config. In node.js, your web server might look like this:

var fs = require("fs")
var https = require("https")
var connect = require("connect")

var app = connect()
    .use(function(req, res){
        res.writeHead(200, {"Content-Type": "application/json"});
        res.end('{"status": "approved"}');
    })

var options = {
    key: fs.readFileSync('ssl/server.key'),
    cert: fs.readFileSync('ssl/server.crt'),
    ca: fs.readFileSync('ssl/ca.crt'),
};

https.createServer(options, app).listen(443);