How to make my CouchbaseMobile iOS app use SSL?

You’re not the only person doing this — it’s got to be super common to set up SSL for servers hosted on AWS. I just don’t have any expertise with it, so I’m not the right person to help; but I would be very surprised if there weren’t a cut-and-dried solution.

You wouldn’t use your IP address. Instead you’d register a domain name for your server and then somehow get AWS to serve the DNS for it so it points to the current address of your server.

I don’t want to put you off, but this seems like something that you can find answers to in the AWS docs or in some other resource online, and it’s also unrelated to Couchbase Mobile. Once you get a domain name and an SSL cert for it, you just configure the server to use the cert. If it’s a traditional cert signed by a known CA, then you have zero work to do on the client besides changing “http:” to “https:” in the replication URL.

I don’t have any kind of formal certification, but I’ve been working with computer security and crypto for about ten years, so I’m pretty sure I know what I’m talking about here.

My understanding is that an acceptable security pattern is to set up a certificate authority and create a root certificate and keep it super secure and safe.

This is the sort of thing a large enterprise like Apple or Google would do. You yourself don’t want to do this; it’s too much work and a security risk. Besides, you won’t find any CA that will sell you a “fertile” root cert that you can use to derive other certs. That is exactly the sort of thing that got the Chinese cert authority in trouble last month, for instance — they sold one of those to an Egyptian(?) company that misused it and opened up a huge security hole.

Believe me, you should just be doing the regular thing: register a domain name, buy an SSL cert for it from a CA, and stick that cert in your server. Job done.

I have never setup SSL on AWS servers myself. I googled around and found this link might be helpful:

Thanks, pasin. But I have already built a number of systems on AWS and know about elastic ip addresses. There are a whole host of other issues in my architecture that I haven’t covered here concerning discovery and deployment that will still present me with the same issue concerning host names/ip addresses, so adapting the certificate to use an elastic ip addresses just avoids the issue that I have to solve in a short while anyway. But thanks, it is appreciated!

I should add that I’ve decided to fork the code and ad an X509 evaluation block that will allow me to deal with the kSecTrustResultRecoverableTrustFailure situation to see if that works the way I like.

Right, that’s the way I should do it, but I can’t for a whole host of reasons I won’t debate. Not the least of which is that I don’t have a team on my side to support all the work that needs to be done to do it correctly. I can’t do it now, so I need a reasonable solution with acceptable risk. I’m not worried about the host name matching issue at this point. If this project survives then someone with much more training will address it in depth. They may even decide that your approach is correct and rip out lots of code and redo my precious work!

I can live with that. :smile:

My training included basic security, and predated SSL, which is new to me, and worked from a focus on risk, and at this point in time the risk/benefit/cost situation doesn’t permit your solution because of what the app is doing. I see no way forward short of providing my own evaluation method as I will not know the host name or IP address during compile time.

Although I’m comfortable with my architecture and plans for security, it occurred to me that adding some extra security to my initial alpha test would not hurt, especially since registering a junk domain name and adding a few lines of code and creating another certificate would be of minimal cost. So I got a domain name and will set up my initial public facing server using the existing name matching for my app testers for any added security it might provide. The other non-public servers will use an additional evaluation block to verify the certificates are signed by my CA’s signing certificate. This gives me the best of both worlds since I can limit alpha users to the one domain and have the flexibility of spinning up multiple servers for other purposes with a sufficient security policy for my purposes that doesn’t need string matching nor lots of certificates.

If the app makes it to the funding stage we’ll get someone with deeper security skills than mine to do a security audit to tease out any problems and provide input for the inevitable re-architecting. So thanks for pushing the idea, it forced me to think through things to see if I could force name matching to occur without major effort and such that it will work for the initial phase. I’ve already tested an initial implementation and it works well. I need to do more testing, and no doubt clean up some loose ends and make sure I’m being efficient, but it all seems to be working now.

Hi, I have also the problem with SSL and self signed cert under iOS.
I did exactly as described here in topic, added cert for my site, self signed, because on our test server.

when I am executing replication it Couchbase Lite 1.1.0 says me
SSL cert is not trustworthy (result=5)
Result 5 is kSecTrustResultRecoverableTrustFailure which is actually ok for self signed certs.

I looked in your source code, and now this code accepted, not sure was that fix for self signed certs or not, but version 1.1.0 reverts this result code. (according to history on github)

Two questions:

  • would it be possible to have latest build and test
  • maybe it is possible to workaround this issue in current version 1.1.0

I’m pretty sure the fix is in 1.1.1, which will be released in the next few days. If you don’t want to wait, you can download the latest nightly build and try it out.

Hi Jens,

I tried latest build from 14.09, but I get the same error message:
CBL_Pusher[https://here is my address]: SSL cert is not trustworthy (result=5)

According to code in github in any case error message should be
Warn(@“CBLCheckSSLServerTrust: SSL cert for %@ is not trustworthy (result=%d)”,
host, result);
and also I should not get this error message for result 5.

I did check twice, removed framework and added it again. plist says me it is 1.1.1.

How can it be?

The short answer is that you’re looking at the source code of the master branch. The 1.1.1 release is built from the branch release/1.1.1.

The warning you’re getting is produced here.

also I should not get this error message for result 5.

Actually you should. Result 5, kSecTrustResultRecoverableTrustFailure, indicates that the cert is self-consistent but not signed by a trusted root, so it can’t be trusted.

It sounds like you didn’t add your cert to the CBLReplication’s list of trusted root certs. Doing that should fix the problem.

Thanks Jens,

Why master branch has code so different to release you are going to issue?

But actually my goal is - it should work, also in 1.1.0 actually.

Again: yes, I have a SELF SIGNED certificate, we have test server, of course it is not trusted by root. Do I have a chance to avoid trust failure with self signed certificate, which is not trusted. I can send you link to our https:// page, if you want.

For Android there is a way with CouchBase Lite to “Trust all”, I did this. for iOS? I have also feeling that CBL does not get my certificate, since error message is the same if I comment addSelfSignedCertificate call.
What I am doing wrong?

My code:

- (void) addSelfSignedCertificate{
NSMutableArray *certs = [NSMutableArray array];
NSString *resourcePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"cert/server.cer"];
if (resourcePath) {
    NSData *certData = [NSData dataWithContentsOfFile:resourcePath];
    SecCertificateRef cert = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData);
    if (cert) {
        [certs addObject:CFBridgingRelease(cert)];
        [CBLReplication setAnchorCerts:certs onlyThese:NO];
    }

- (void) replicateDatabase:(NSString*)url withFilter:(NSString*)filter username:(NSString*)username password:(NSString*)password once:(BOOL)once toResult:(void(^)(NSString* result, NSError* error))returnResultBlock{
NSError* dbInitError = [self checkDatabaseInited];
if(!dbInitError){
    [self addSelfSignedCertificate];
    NSURL* remoteSyncURL = [NSURL URLWithString: url];
    CBLReplication *pull, *push;
    pull = singlePull = [database createPullReplication: remoteSyncURL];
    push = singlePush = [database createPushReplication: remoteSyncURL];
    push.continuous = pull.continuous = NO;

    if(username && ![username isKindOfClass:[NSNull class]] && ![username isEqualToString:@""]){
        id<CBLAuthenticator> auth;
        auth = [CBLAuthenticator basicAuthenticatorWithName: username password: password];
        pull.authenticator = push.authenticator = auth;
    }
    pull.filter = filter;
    push.filter = @"quotes";
    [[NSNotificationCenter defaultCenter] addObserver: self
                                     selector: @selector(replicationChanged:)
                                         name: kCBLReplicationChangeNotification
                                       object: push];
    [[NSNotificationCenter defaultCenter] addObserver: self
                                     selector: @selector(replicationChanged:)
                                         name: kCBLReplicationChangeNotification
                                       object: pull];
    [push start];
    [pull start];
    returnResultBlock(@"Done", nil);
} else {
    returnResultBlock(nil, dbInitError);
}

}

Because master is for 1.2. The 1.1.1 branch is a continuation of 1.1 with specific bug fixes cherry-picked to it. This is a pretty typical development workflow for a large project.

For Android there is a way with CouchBase Lite to “Trust all”, I did this.

Really? I wasn’t aware of such a method. That’s a bad idea, as it removes most of the security from SSL. You still have encryption, but no assurance that you’re talking to the real server.

My code:

Set a breakpoint and make sure the addAnchorCerts: call is actually being reached.

Double-check that the cert data matches the cert in use on your server.

Hi Jens,

Of course I tripple checked cert is added to the list and see that array has count 1 with data.
Thanks for link to code, now I see how my cert should be trusted. Maybe problem is in cert file itself?

I tried different site. For example take any https site in the world, which has approved CA certificate - no problem, I get an error “not found”, but connection works. And actually it is not needed to import any certificate in App, it works, how it should be.

Then I tried any site which has self signed certificate. Any, also without couch db. Not sure how to export certificate on mac, but I can export certificate on Windows, in DER format and with .CER extension. Add this certificate as per code above, and simply try to replicate with server, where I got this certificate.

Always I got an error SSL cert is not trustworthy (result=5). And it does not matters do I add cert in Array or not.

Do you have a working example with sample self signed certificate? Maybe cert I export in wrong format?

Yes you are completely right, this is a security risk, and I will never deploy my app in production without correct SSL. But for testing purposes we have only self signed…

The cert should be in raw binary (DER) format, not PEM or whatever.

The Couchbase Lite unit tests include replication tests against a Sync Gateway server using a known self-signed SSL cert; the test bundle contains a copy of the cert and adds it to the replication root-cert list just the way you’re doing. (Example)

Hi Jens,

Ok now I could export my self signed cert on mac, it has exactly the same format as SelfSigned.cer in your tests. It still does not work with the same error code: kSecTrustResultRecoverableTrustFailure.

I noted also that your test certificate issued for localhost. If I look to your sources version 1.1.1, then I could see you force trust if you get kSecTrustResultRecoverableTrustFailure for localhost. But of course not for all. That’s why probably your tests are OK for localhost, but did you try this code with real site with self signed certificate.

code:

if (result == kSecTrustResultRecoverableTrustFailure) {
// Accept a self-signed cert from a local host (".local" domain)
if (SecTrustGetCertificateCount(trust) == 1 &&
([host hasSuffix: @".local"] || [host hasSuffix: @".local."])) {
result = kSecTrustResultUnspecified;
}
}
if (result != kSecTrustResultProceed && result != kSecTrustResultUnspecified) {
Warn(@"%@: SSL cert is not trustworthy (result=%d)", self, result);
return NO;
}

can I send you my certificates? I have actually tried 2 different self signed hosts and certificates…

If the cert has been added to the trusted-roots list, the status should be kSecTrustResultProceed not kSecTrustResultRecoverableTrustFailure, as far as I know.

Sure, you can send me your cert (jens at couchbase) and I’ll try it out. Is the host publicly reachable?

I noticed that my self signed certificate with an IP address as the FQDN does work on iOS8.x
I checked the replication log and it is connecting via HTTPS.
But it fails on iOS9

@edumos, are you aware of App Transport Security? You’ll have to update your app’s Info.plist to allow self-signed certs.

@jens yes I’m aware but I should have pointed out in my previous post that the app is build for iOS8.4 with XCode 6.4 So in theory it should work on iOS9 without any adjustment. I did try adding the keys to the plist in order to disable ATS but as expected this did not have any effect.
So apparently setting the anchor certs in the CBLReplication is not working if the app is build for iOS8 (XCode 6.4) and deployed onto a iOS9 device.
I’m looking deeper into this problem but I hoped to find an answer in this forum.