Skip to content
Jens Alfke edited this page Jul 20, 2015 · 8 revisions

Enabling SSL In The Listener

If your application uses the Couchbase Lite Listener to accept incoming connections, so that peers can access or replicate with your app's database, it's easy to support SSL. This will improve security and privacy by encrypting the network traffic.

Generally the biggest headache in setting up an SSL server is getting a certificate. A certificate used by a website will be signed by a Certificate Authority (CA) that can vouch for its identity. This involves creating a certificate request, submitting it to the authority, doing some sort of challenge/response that lets the authority verify that you own the domain in question, receiving the signed certificate, and then installing it on the computer that will serve requests. (The Sync Gateway wiki contains more information on doing this, if you're curious.)

If you've gone through all that, you can use that certificate with CBLListener pretty easily. First use the Keychain API to get a SecIdentityRef, which is a reference to your certificate plus its matching private key. Then set that as the SSLIdentity property of the listener.

But fortunately, for most purposes we don't need to go through that rigamarole. Identity is a complicated topic, and it's hard to say how a mobile device should identify itself. So we'll put that out of the way and just focus on encryption. For that, any certificate will do, even a so-called self-signed one. Creating a self-signed certificate is just a simple matter of programming, specifically generating an RSA key-pair and wrapping the public key in an X.509 certificate. Couchbase Lite 1.1 can do that for you.

How To Do It

"Server" side

We assume you've already instantiated a CBLListener object. Before calling its -start method, configure its SSL identity like so:

NSError* error;
if (![listener setAnonymousSSLIdentityWithLabel: @"MyApp SSL" error: &error])
    [self handleError: error];

That's it. Your listener is now serving SSL using an automatically generated identity. (The label string is unimportant; it's just used as an identifier to store and look up the certificate in the app's Keychain.)

Client side

When you try to replicate with a peer that's got SSL set up as above, the replication will initially fail because the certificate isn't trusted. (Which it isn't, because the peer just made it up out of thin air.)

Note: In versions after 1.1, this problem won't occur as long as the hostname of the replication URL ends with .local (which is used by Bonjour for devices on the local network.) So if you're using Bonjour to discover peers, you won't have SSL trust issues.

To get around this, the client can get a copy of the server cert from the CBLReplication, then tell the replicator to trust the cert by calling the setAnchorCerts method, and then start the replication again.

Wait, Is This Secure?

Yes and no. It encrypts the connection, which is unquestionably much better than not using SSL. But unlike the usual SSL-in-a-browser approach you're used to, it doesn't identify the server/listener to the client. The client has to take the cert on faith the first time it connects.

However, the client can save the certificate persistently, and reuse it the next time it syncs with that peer. (This is called "cert pinning".) So from then on, it can be sure that it's always syncing with the same peer as the first time. Often that's good enough.

There are ways to validate even the initial connection by using a "pairing" process where the peers first exchange information through a trusted channel. For example, the device running the listener can generate and display a QR code that contains its IP address and a SHA-1 digest of its SSL certificate; the other peer can scan that code using its camera. Then when the second peer connects it can verify that the serverCertificate has the expected digest.

Client Certificate Authentication

Usually it's only the SSL server that uses a certificate to identify itself. But SSL also supports client certificates, which work in reverse, allowing the client to present a certificate that identifies itself to the server.

Note: This feature was added in early July 2015 (commit e849878), after version 1.1. The API shown below is provisional and may change before appearing in a release.

Client side

  1. Call [CBLAuthenticator SSLClientCertAuthenticatorWithAnonymousIdentity:]. This does the following:
    • The first time it's called, it generates a self-signed certificate (and private key) in the app's Keychain, tagged with the label you provided.
    • On subsequent calls, it reads the certificate and key out of the Keychain.
    • It wraps them up in a CBLAuthenticator object.
  2. Set the returned CBLAuthenticator as the replication's authenticator property.

Example:

id<CBLAuthenticator> auth = [CBLAuthenticator SSLClientCertAuthenticatorWithAnonymousIdentity: @"me"];
pullRepl.authenticator = pushRepl.authenticator = auth;

The string "me" doesn't really mean anything, and it won't be visible to the server. It's just a way of labeling different identities in the Keychain, should you need to use more than one.

"Server" side

To check client certs on the server side (i.e. the app that's running the listener), add a delegate to the CBLListener and implement -[authenticateConnectionFromAddress:withTrust:]. This is passed a SecTrustRef object from which you can get the client cert.

Here's an example that assumes there's a single client cert, allowedClientCert, that you'll accept.

- (NSString*) authenticateConnectionFromAddress: (NSData*)address
                                      withTrust: (SecTrustRef)trust
{
    SecCertificateRef clientCert = SecTrustGetCertificateAtIndex(trust, 0);
    if (CFEqual(clientCert, self.allowedClientCert) {
        return @"user"; // allow connection; exact string doesn't matter
    } else {
        return nil;  // deny connection
    }
}

The method returns a string; for now it's only significant whether the value is nil (meaning deny) or non-nil (meaning allow.) In the future we may extend the API in ways that will let you use this string to identify the user in later callbacks.

App Transport Security (iOS 9, OS X 10.11)

Apple's new App Transport Security feature requires that applications open only SSL connections, and that those connections follow current best practices. We've had to make a few changes since Couchbase Lite 1.1 to align our SSL support with those best practices, as described in issues #815 and #816. The compatibility problems had to do with accepting self-signed server certs, and with the SSL support in the CBLListener.

If you're using CBL 1.1 or earlier and need to accept a self-signed server cert, or to use P2P, your best bet is to add the necessary exceptions to App Transport Security, as described in Apple's technote.

If you can update CBL to a compatible version (at this point that requires checking out and building from the master branch), you'll be fine unless your app tries to connect to a peer running iOS 8 or OS X 10.10. If it does, your app will need to add an ATS exception to its Info.plist to turn off the requirement for forward secrecy in the SSL cipher. This is because the older OS's don't support the necessary ciphers. The property add to the Info.plist looks like:

NSAppTransportSecurity = {
    NSExceptionDomains = {
        local = {
            NSIncludesSubdomains = YES,
            NSExceptionRequiresForwardSecrecy = NO
        }
    }
}

(The domain local corresponds to Bonjour local-network services.)

Clone this wiki locally