Cocoa: SQLCipher Manager Available

2010-01-05 19:00:00 -0500


We’ve posted a new project to Github: SQLCipher Manager.

I’ve got to make this brief because I have a ton of work to do, but we’ve recently split out a few chunks of re-usable code from our iPhone application Strip in order to facilitate sharing that code across multiple projects, both for the iPhone and for Mac OS X development. Most of that stuff will be staying internal, and wouldn’t be of much use to others anyway, but we decided to make SQLCipher Manager available as open source software under the same MIT-style license as SQLCipher itself.

The project on Github is an XCode project that builds a static library. You can use cross-project references to utilize this code from other XCode projects and to link against the static lib (required by Apple for us iPhone developers). There’s a great reference on doing this over at Mobile Orchard, so I’ll refrain fro going into the process here.

In short, it provides a singleton that handles your database connection to SQLCipher, keying, re-keying, notifying your app when the database has been created or opened, etc. This sounds like a no-brainer, but you end up re-using this code quite a bit, so it only made sense to define it and a delegate protocol:


@protocol SQLCipherManagerDelegate <NSObject>
@optional
- (void)didOpenDatabase;
- (void)didCreateDatabase;
- (void)didEncounterRekeyError;
- (void)didEncounterDatabaseError:(NSString *)error;
@end

This means you’d do something like this in your app delegate, to initialize the manager for your application, and handle schema events:


- (void)applicationDidFinishLaunching:(UIApplication *)application {
NSLog(@"Setting up sqlcipher manager, and ourself as delegate...");
// setup the db manager register as the delegate
SQLCipherManager *dm = [SQLCipherManager sharedManager];
[dm setDelegate:self];
// set the databasePath property (required), use default iphone app documents directory
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *saveDirectory = [paths objectAtIndex:0];
NSString *databasePath = [saveDirectory stringByAppendingPathComponent:CONFIG_DBNAME];
NSLog(@"setting db path to %@", databasePath);
[dm setDatabasePath:databasePath];

[self launchLoginView];
}

# pragma mark -
# pragma mark SQLCipherManagerDelegate

- (void)didOpenDatabase {
NSLog(@"Database opened");
SQLCipherManager *dm = [SQLCipherManager sharedManager];
NSUInteger schemaVersion = [dm getSchemaVersion];
if (schemaVersion != CONFIG_SCHEMAVERSION) {
NSLog(@"handing off to schema manager, found old version %d", schemaVersion);
StripSchemaManager *schemaManager = [[StripSchemaManager alloc] initWithSQLCipherManager:dm];
[schemaManager migrate:CONFIG_SCHEMAVERSION];
[schemaManager release];
}
}

- (void)didCreateDatabase {
NSLog(@"Database created, migrating");
SQLCipherManager *dm = [SQLCipherManager sharedManager];
StripSchemaManager *schemaManager = [[StripSchemaManager alloc] initWithSQLCipherManager:dm];
[schemaManager migrate:CONFIG_SCHEMAVERSION];
}

- (void)didEncounterDatabaseError:(NSString *)error {
NSLog(@"Database error! %@", error);
SQLCipherManager *dm = [SQLCipherManager sharedManager];
[dm closeDatabase];
abort(); // ouch, yo, but it leaves us a crash log for later
}

- (void)didEncounterRekeyError {
[self lockApplication];
[self presentError:[NSError errorUsingDatabase:@"An error occured rekeying the database. Please login again using your original password" reason:@""]];
}

Your login view controller (or whatever you are passing control to as the application sets up) would actually do the opening, or creating, of the database. Ours looks something like this:


- (IBAction)submit {
SQLCipherManager *dm = [SQLCipherManager sharedManager];
BOOL databaseExists = [dm databaseExists];

// just bail if the submitted password is invalid
if (![dm passwordIsValid:passwordField.text]) {
errorLabel.text = @"Invalid password";
self.passwordConfirm = nil;
passwordField.text = nil;
return;
}

// if this is the first time through, require confirmation
if(databaseExists == NO && !self.passwordConfirm) {
self.passwordConfirm = passwordField.text;
passwordField.text = nil;
passwordField.placeholder = @"Confirm your password";
[passwordField becomeFirstResponder];
return;
}

loadingImage.hidden = NO;
CFRunLoopRunInMode (kCFRunLoopDefaultMode, 0, true);

// seed OpenSSL RAND (removed)

SecureAppDelegate *appDelegate = (SecureAppDelegate *) [[UIApplication sharedApplication] delegate];
if (databaseExists) { // what mode are we in?
// attempt login, we only have passwordField present on screen
if ([dm openDatabaseWithPassword:passwordField.text]) {
[passwordField resignFirstResponder];
[appDelegate launchAppView]; // push new view controller, etc... wait we have no navigation controller...
} else {
errorLabel.text = @"Incorrect password";
[passwordField becomeFirstResponder];
}
} else {
if ([self passwordIsConfirmed]) {
[passwordField resignFirstResponder];
[dm createDatabaseWithPassword:passwordField.text];
[appDelegate launchAppView]; // here again we need to get our navigation controller going
} else {
errorLabel.text = @"Passwords didn't match";
[passwordField becomeFirstResponder];
}
}
passwordField.placeholder = @"Access password";
self.passwordConfirm = nil; // wipe fields
passwordField.text = nil;
loadingImage.hidden = YES;
}

Those are the basics of using SQLCipher in a Cocoa Touch application, and the approach is easily ported to Mac OS X. Can’t go into the various details right now, hopefully it’s explanatory enough. Questions should be posed on the SQLCipher-Users mailing list. Bugs and enhancement requests should be filed using the Issues tracker on the Github project.

Zetetic is the creator of the encrypted iPhone data vault and password manager Strip and the open source encryption-enhanced database engine SQLCipher.

blog comments powered by Disqus