How to make my CouchbaseMobile iOS app use SSL?


#1

I’m at the point where I want to put my iOS app out for testing over the internet, so I have a server using SSL. Looking around at comments it seems like CouchbaseMobile will support using certificates for this, but I can find an example or documentation on how to do this.

I’ve used openssl to create a self-signed server pem file, and it works well with a browser or curl when I tell it to trust the signing. How do I get CouchbaseMobile to see my certificate? And is there a hook or callback that will let me deal with evaluating the server trust myself, or some way to tell it what certificate I trust?

–fran


#2

I think if you Create a NSURLCredential with your certs and then a NSURLProtectionSpace defining your host etc and then store them together in the shared NSURLCredentialStorage, then it might just work as the ios NSURL libraries used by CBL will use this for connections to you host.


#3

There is a +setAnchorCerts:onlyThese: method in CBLReplication that you can use. Basically you would need to include your server certificate into your application and anchor that certificate.

Method definition:

/** Adds additional SSL root certificates to be trusted by the replicator, or entirely overrides the OS's default list of trusted root certs.
    @param certs  An array of SecCertificateRefs of root certs that should be trusted. Most often these will be self-signed certs, but they might also be the roots of nonstandard CAs.
    @param onlyThese  If NO, the given certs are appended to the system's built-in list of trusted root certs; if YES, it replaces them (so *only* the given certs will be trusted.) */
+ (void) setAnchorCerts: (NSArray*)certs onlyThese: (BOOL)onlyThese;

Sample:

- (NSArray*) remoteTestDBAnchorCerts {
    NSData* certData = [NSData dataWithContentsOfFile: [self pathToTestFile: @"SelfSigned.cer"]];
    SecCertificateRef cert = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData);
    return @[CFBridgingRelease(cert)];
}

NSArray* serverCerts = [self remoteTestDBAnchorCerts];
[CBLReplication setAnchorCerts: serverCerts onlyThese: NO];

#4

Thanks, Paul, I’m off to research these classes and how they work, I totally missed them on my first pass.

–fran


#5

Pasin, thanks for code and pointing out the CBLReplication method, for some reason I totally ignored CBLReplication, fooling myself into thinking that the certs would be referenced elsewhere! I’m off to give this a try.


#6

I’ve added certificates in both ways, first so that I can verify the database exists, using AFNetworking to try to call the REST API to get the database names, and that works with SSL. Now I am trying to verify that CouchbaseMobile is working, but when I start up my replication object it stops immediately. I’ve added my certificates, seemingly successfully, using setAnchorCerts in my initialization.

It seems like the replications are having some kind of problem. Is there a way to get some debug info? All I have now is a notification that the replication stopped as soon as I invoke the start method, then it goes idle.


#7

Turn on logging. You’ll want the Sync logging keyword, to start.


#8

Totally missed the logging, that sounds like it would be very helpful. How is that done? Is there some documentation on it, or at least a class that I can start with?

As a side note, I find it time consuming to locate the docs for CouchbaseMobile, I seem to have to click around for a while to find them, it would be nice if there was a link off the main page.


#9

OK I found out how to do it in the forum, the logging section doesn’t mention the method:

[CBLManager enableLogging:@“Sync”];
[CBLManager enableLogging:@“SyncVerbose”];


#10

I find it time consuming to locate the docs for CouchbaseMobile

There’s a “Read the documentation” link at the bottom of http://developer.couchbase.com/mobile/ … I also recommend making a bookmark in your browser.

The docs could be better, though. The best guide to logging is still in the Github wiki.


#11

Ah, I missed that link, Thanks, Jens! As far as I can tell all docs could be better, mine certainly!

I’ve turned on logging for code that works well without SSL, but which doesn’t seem to work for SSL. Here is the output during start, just here for completeness. I changed my path to //…DBpath…// and this method handles connections and disconnections and replication in my Repository class.Oh, it deletes the local database if in debug mode, as it is here.

16:29:26.677| CBLDatabase: Created CBLManager[0x7feec2d48dc0 //..DBpath..//]
16:29:26.678| CBLDatabase: CBLManager[0x7feec2d48dc0 //..DBpath..//] is the sharedInstance
16:29:26.679| CBLDatabase: Opening CBLDatabase[<0x7feec2c3da60>ts1]
16:29:26.679| CBLDatabase: Open //..DBpath..///ts1.cblite (flags=200006)
 -[Repository connectTo:withDatabase:] [Line 164] #$#$#$[[ ERASING APP LOCAL DATABASE ]]#$#$#$
16:29:26.682| CBLDatabase: Deleting //..DBpath..///ts1.cblite
16:29:26.682| CBLDatabase: Closing <0x7feec2c3da60> //..DBpath..///ts1.cblite
16:29:26.683| CBLDatabase: Deleted file //..DBpath..///ts1.cblite
16:29:26.684| CBLDatabase: Deleted file //..DBpath..///ts1 attachments
 -[Repository connectTo:withDatabase:] [Line 178] Connecting to Server database....
16:29:26.685| CBLDatabase: Opening CBLDatabase[<0x7feec2e2f8c0>ts1]
16:29:26.685| CBLDatabase: Open //..DBpath..///ts1.cblite (flags=200006)
16:29:39.970| CBLDatabase: Created CBLManager[0x7feec2c4a6f0 //..DBpath..//]
16:29:39.971| CBL_Server: CBL_Server[0x7feec2c4a9b0] Starting server thread ...
16:29:39.971| CBLDatabase: CBLManager[0x7feec2d48dc0 //..DBpath..//] created CBL_Server[0x7feec2c4a9b0] (with CBLManager[0x7feec2c4a6f0 //..DBpath..//])

At this point the code has created the database from CBLManager and has just set up the replication and hooked up notifications to my replicationProgress method and will exit:

16:29:39.971| Sync: CBLReplication[to https://192.168.1.13:6984/ts1/]: offline, progress = 0 / 0, err: (null)
 -[Repository replicationProgress:] [Line 278] ****** pullReplication is stopped. ******
 -[Repository replicationProgress:] [Line 292] pushReplication is offline.
16:29:39.971| Sync: CBLReplication[from https://192.168.1.13:6984/ts1/]: offline, progress = 0 / 0, err: (null)
16:29:39.971‖ CBL_Server: CBL_Server[0x7feec2c4a9b0]: Server thread starting...
 -[Repository replicationProgress:] [Line 280] pullReplication is offline.
 -[Repository replicationProgress:] [Line 292] pushReplication is offline.
 -[Repository connectTo:withDatabase:] [Line 220] Connection method finished

At about this point the replicationProgress reports that both are active as they pull down a few hundred items. The address is the database, ts1. If I post this into a browser I get the expected JSON response, after accepting the certificate. So, I suspect that my certificate isn’t being found. I don’t see any complaints when, during class init, I add the certs as suggested above by pasin, like this: [CBLReplication setAnchorCerts:certs onlyThese:NO];

Here is what follows. My expectation is that the replication will pull in the docs and I’ll see some logging in my replicationProgress method. Any ideas? How can I verify the certificates are being added/used/tried?

16:29:39.973‖ CBLDatabase: Opening CBLDatabase[<0x7feec2d4c430>ts1]
16:29:39.973‖ CBLDatabase: Open //..DBpath..///ts1.cblite (flags=200006)
16:29:39.975‖ Sync: CBL_Pusher[https://192.168.1.13:6984/ts1/] STARTING ...
16:29:39.978‖ Sync: CBL_Pusher[https://192.168.1.13:6984/ts1/]: Reachability state = <192.168.1.13>:reachable (30002), suspended=0
16:29:39.979‖ Sync: CBL_Pusher[https://192.168.1.13:6984/ts1/]: Going online
16:29:39.990‖ Sync: CBL_Pusher[https://192.168.1.13:6984/ts1/] Progress: set active = 1
16:29:39.991‖ SyncVerbose: CBL_Pusher[https://192.168.1.13:6984/ts1/]: postProgressChanged (0/0, active=1 (batch=0, net=1), online=1)
16:29:39.991‖ SyncVerbose: CBL_Pusher[https://192.168.1.13:6984/ts1/]: GET _local/901a6485814f57adff63a01698416375192e5547
16:29:39.991‖ RemoteRequest: CBLRemoteJSONRequest[GET https://192.168.1.13:6984/ts1/_local/901a6485814f57adff63a01698416375192e5547]: Starting...
16:29:39.991‖ SyncVerbose: CBL_Pusher[https://192.168.1.13:6984/ts1/]: postProgressChanged (0/0, active=1 (batch=0, net=1), online=1)
16:29:39.992‖ Sync: CBL_Puller[https://192.168.1.13:6984/ts1/] STARTING ...
16:29:39.994‖ Sync: CBL_Puller[https://192.168.1.13:6984/ts1/]: Reachability state = <192.168.1.13>:reachable (30002), suspended=0
16:29:39.994‖ Sync: CBL_Puller[https://192.168.1.13:6984/ts1/]: Going online
16:29:39.994‖ Sync: CBL_Puller[https://192.168.1.13:6984/ts1/] Progress: set active = 1
16:29:39.994‖ SyncVerbose: CBL_Puller[https://192.168.1.13:6984/ts1/]: postProgressChanged (0/0, active=1 (batch=0, net=1), online=1)
16:29:39.994‖ SyncVerbose: CBL_Puller[https://192.168.1.13:6984/ts1/]: GET _local/2197320788ddbd84be29d3458108bc0473680b5a
16:29:39.994‖ RemoteRequest: CBLRemoteJSONRequest[GET https://192.168.1.13:6984/ts1/_local/2197320788ddbd84be29d3458108bc0473680b5a]: Starting...
16:29:39.994‖ SyncVerbose: CBL_Puller[https://192.168.1.13:6984/ts1/]: postProgressChanged (0/0, active=1 (batch=0, net=1), online=1)
16:29:40.001‖ RemoteRequest: CBLRemoteJSONRequest[GET https://192.168.1.13:6984/ts1/_local/901a6485814f57adff63a01698416375192e5547]: Got response, status 404
16:29:40.001‖ Sync: CBL_Pusher[https://192.168.1.13:6984/ts1/]: Replicating from lastSequence=(null)
16:29:40.002‖ SyncVerbose: CBL_Pusher[https://192.168.1.13:6984/ts1/]: Received 0 revs
16:29:40.003‖ Sync: CBL_Pusher[https://192.168.1.13:6984/ts1/] Progress: set active = 0
16:29:40.003‖ SyncVerbose: CBL_Pusher[https://192.168.1.13:6984/ts1/]: postProgressChanged (0/0, active=0 (batch=0, net=0), online=1)
16:29:40.004‖ RemoteRequest: CBLRemoteJSONRequest[GET https://192.168.1.13:6984/ts1/_local/2197320788ddbd84be29d3458108bc0473680b5a]: Got response, status 404
16:29:40.004‖ Sync: CBL_Puller[https://192.168.1.13:6984/ts1/]: Replicating from lastSequence=(null)
16:29:40.004‖ SyncVerbose: CBL_Puller[https://192.168.1.13:6984/ts1/] starting ChangeTracker: mode=0, since=(null)
16:29:40.005‖ ChangeTracker: CBLSocketChangeTracker[0x7feec2e63d60 ts1]: Starting...
16:29:40.005‖ SyncVerbose: CBLSocketChangeTracker[0x7feec2e63d60 ts1]: GET //192.168.1.13:6984/ts1/_changes?feed=normal&heartbeat=300000&style=all_docs
16:29:40.007‖ ChangeTracker: CBLSocketChangeTracker[0x7feec2e63d60 ts1]: Started... <https://192.168.1.13:6984/ts1/_changes?feed=normal&heartbeat=300000&style=all_docs>
16:29:40.008‖ ChangeTracker: CBLSocketChangeTracker[0x7feec2e63d60 ts1]: Event 1 on <__NSCFInputStream: 0x7feec2e652c0>

#12

Sorry, I accidentally posted twice! I’ll edit this second one to add more info.

I just compared the SSL log versus the non-SSL log, and the primary difference seems to be:

16:29:39.978‖ Sync: CBL_Pusher[https://192.168.1.13:6984/ts1/]: Reachability state = <192.168.1.13>:reachable (30002), suspended=0

17:40:47.670‖ Sync: CBL_Pusher[http://192.168.1.55:5984/ts1/]: Reachability state = <192.168.1.55>:reachable (20002), suspended=0

These reachable codes are different, 20002 works, 30002 does not. Any idea what that indicates?


#13

Hm. Those logs prove conclusively that your SSL cert is working, because if it weren’t, you wouldn’t see any connections at all, just warnings/errors about SSL errors and invalid certs.

As for the difference in reachability flags, it doesn’t make any difference to us because none of the flags we pay attention to have changed. It looks like the difference is 0x10000, which appears to be kSCNetworkReachabilityFlagsIsLocalAddress, meaning " the specified nodename or address is one associated with a network interface on the current system." Presumably that’s because the host running the test is 192.168.1.13.


#14

Thanks, I am glad that confirms that the SSL is working as I’m not very experienced with that technology.

Do you have any ideas for debugging this? The only difference in SSL vs. non-SSL instances of my code is the URL.

Is there an example app that uses replication that works in both SSL and non SSL that I can test? I can then install it here at the same addresses to verify that it works as expected, that might help me focus on the problem area.


#15

SSL operates at the TCP socket level and encrypts all data sent over the socket; it doesn’t actually care what protocol it is. When you use an “https:” URL, the HTTP implementation (NSURLConnection) transparently inserts an SSL encryption layer on top of the socket it uses. Once that’s been established, everything works identically, all the HTTP requests are sent as usual.

What are you using as the server? It doesn’t appear to be Sync Gateway (I’m reading between the lines looking at the exact REST calls being made by CBL.)

The long log you posted looks normal but stops at the point where it’s just starting to receive the list of changes from the server. Are you implying that nothing else ever gets logged after ChangeTracker: CBLSocketChangeTracker[0x7feec2e63d60 ts1]: Event 1… ?


#16

Nothing else happens in the log after the above, except that there are a number of “ChangeTrackerVerbose: CBLSocketChangeTracker HasBytesAvailable” messages with no details, then eventually nothing and my notifications report that the replication is not active.

This is an unfunded alpha app that was prototyped with CouchDB as a data server. When I asked in this forum how to get it running with SSL for use on AWS, suggestions were made to try SyncGateway with a link to a blog on installing. But I couldn’t successfully install it the on AWS, nor could I find a AMI for AWS that worked or was documented sufficiently for me to try. Eventually I found a Docker based CouchDB that I could run on AWS or a local VM, so after adding some modifications, that became my server for alpha testing.

CBL is a great package, it deals with a lot of the complexity of replication in a nice way. I’ve done years of work on document replication and synchronization, so I am aware of the issues and I like this solution for mobile, it strikes a good balance. The fact that it works with CouchDB and offers a transition to SyncGateway was a great selling point. You guys should seriously support a registered Docker solution that is plug-n-play for those of us who don’t have the time to plug away at it. SyncGateway looks like a good solution for scaling my app once I get users, but I don’t have funding or revenue or an ops guy, so it will have to wait. Right now I depend on using the AWS free tier and CouchDB since it is unambiguously free and I can actually get it installed anywhere via Docker.

So, yes, I am using CouchDB, and it runs from Docker on port 5984 as http, and 6984 as https. When I use curl or a browser it responds as expected on either port. When I use AFNetworking in my app to make a REST call to get the names of the databases, it responds on both ports as expected. When I use CBL to create documents and sync my app to the server database it works on 5984, but on 6984 it stalls on replication without any details about why. My app first replicates before allowing new documents to be made, so I have not tested that. The code is the same, only the URL is different. I’m not sure how to debug this short of ripping the framework out and adding the source from github into the project and stepping into the code to see if I can detect where the problem is coming from.

If there were a working example project that has been verified to work in both modes, then I could test that here to verify that it is or isn’t something local. As it is, I have never seen replication work with SSL, so it seems that the problem is in that area. Does that make sense?


#17

Paul, I’ve implemented the credentials you suggest and stored them marked as NSURLCredentialPersistencePermanent, and I set the protection space to NSURLAuthenticationMethodDefault, but some of my replications and straight AFNetworking calls seem to ignore it.

I thought that AFNetworking and CBL would use the credentials registered for the same host/port/protocol, am I wrong?

Here is my code, I call it before any networking code is explicitly invoked:

NSURLCredential* cred = [NSURLCredential credentialWithUser: @"nm" password: @"pw"
 persistence: NSURLCredentialPersistencePermanent];

NSURLProtectionSpace* space = [[NSURLProtectionSpace alloc] 
 initWithHost: @"127.0.0.1" port: 5984 protocol: @"http" realm: @"administrator"
 authenticationMethod: NSURLAuthenticationMethodDefault];

[[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential: cred 
 forProtectionSpace: space];

#18

Looking at the logs some more, it looks like the WebSocket calls are not using authentication, or stop using it. The docs are scanty on this, what is supposed to be happening?


#19

If you want the credential to be used over HTTPS, I believe you have to set the protocol: parameter in the above snippet to @"https".


#20

Exactly, but since I’m having trouble getting replication working with https, I’ve decided to focus on the authentication part using plain http until I get a good solid block of time to trace though the replication to see why it is stalling. As I mentioned below, mostly the authentication works, but credentials seem to stop being sent after a time, and the logs show WebSockets not sending any authentication at some point. Again, I need to put some time aside to trace through this to see if I can isolate the problem.