Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cipher Migrate feature for iOS and Android #113

Open
wants to merge 3 commits into
base: latest
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/android/io/sqlc/SQLiteAndroidDatabase.java
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,34 @@ void open(File dbfile, String key) throws Exception {
mydb = SQLiteDatabase.openOrCreateDatabase(dbfile, key, null);
}

/**
*
* Open a database.
*
* @param dbfile The database File specification
*/
void open(File dbfile, String key, boolean cipherMigrate) throws Exception {
try {
mydb = SQLiteDatabase.openOrCreateDatabase(dbfile, key, null);
} catch (RuntimeException e) {
if (cipherMigrate) {
mydb = SQLiteDatabase.openOrCreateDatabase(dbfile, key, null, new SQLiteDatabaseHook() {
@Override
public void preKey(SQLiteDatabase sqLiteDatabase) {

}

@Override
public void postKey(SQLiteDatabase sqLiteDatabase) {
sqLiteDatabase.query("PRAGMA cipher_migrate");
}
});
} else {
throw e;
}
}
}

/**
* Close a database (in the current thread).
*/
Expand Down
18 changes: 15 additions & 3 deletions src/android/io/sqlc/SQLitePlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ private void startDatabase(String dbname, JSONObject options, CallbackContext cb
*
* @param dbName The name of the database file
*/
private SQLiteAndroidDatabase openDatabase(String dbname, String key, CallbackContext cbc, boolean old_impl) throws Exception {
private SQLiteAndroidDatabase openDatabase(String dbname, String key, CallbackContext cbc, boolean cipherMigrate) throws Exception {
try {
// ASSUMPTION: no db (connection/handle) is already stored in the map
// [should be true according to the code in DBRunner.run()]
Expand All @@ -225,7 +225,7 @@ private SQLiteAndroidDatabase openDatabase(String dbname, String key, CallbackCo
Log.v("info", "Open sqlite db: " + dbfile.getAbsolutePath());

SQLiteAndroidDatabase mydb = new SQLiteAndroidDatabase();
mydb.open(dbfile, key);
mydb.open(dbfile, key, cipherMigrate);

// NOTE: NO Android locking/closing BUG workaround needed here
cbc.success();
Expand Down Expand Up @@ -322,6 +322,7 @@ private boolean deleteDatabaseNow(String dbname) {
private class DBRunner implements Runnable {
final String dbname;
final String dbkey;
final boolean cipherMigrate;

final BlockingQueue<DBQuery> q;
final CallbackContext openCbc;
Expand All @@ -342,13 +343,24 @@ private class DBRunner implements Runnable {
}
this.dbkey = key;

boolean cipherMigrate = false;
if (options.has("cipherMigrate")) {
try {
cipherMigrate = options.getBoolean("cipherMigrate");
} catch (JSONException e) {
// NOTE: this should not happen!
Log.e(SQLitePlugin.class.getSimpleName(), "unexpected JSON error getting password cipherMigrate, ignored", e);
}
}
this.cipherMigrate = cipherMigrate;

this.q = new LinkedBlockingQueue<DBQuery>();
this.openCbc = cbc;
}

public void run() {
try {
this.mydb = openDatabase(dbname, this.dbkey, this.openCbc, false);
this.mydb = openDatabase(dbname, this.dbkey, this.openCbc, this.cipherMigrate);
} catch (Exception e) {
Log.e(SQLitePlugin.class.getSimpleName(), "unexpected error, stopping db thread", e);
dbrmap.remove(dbname);
Expand Down
130 changes: 110 additions & 20 deletions src/ios/SQLitePlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -178,34 +178,33 @@ -(void)openNow: (CDVInvokedUrlCommand*)command
// NOTE: create DB from resource [pre-populated] NOT supported with sqlcipher.

if (sqlite3_open(name, &db) != SQLITE_OK) {
[self logSqlError:db message:@"Unable to open DB"];
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Unable to open DB"];
[self.commandDelegate sendPluginResult:pluginResult callbackId: command.callbackId];
return;
} else {
sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, 1, NULL);

#if 0
sqlite3_create_function(db, "REGEXP", 2, SQLITE_ANY, NULL, &sqlite_regexp, NULL, NULL);
#endif

// SQLCipher key:
NSString *dbkey = [options objectForKey:@"key"];
const char *key = NULL;
if (dbkey != NULL && dbkey.length != 0) key = [dbkey UTF8String];
NSLog((key != NULL) ? @"Open DB with encryption" : @"Open DB with NO encryption");
if (key != NULL) sqlite3_key(db, key, strlen(key));

[self prepareDatabase:db options:options];
// XXX Brody TODO check this in Javascript instead.
// Attempt to read the SQLite master table [to support SQLCipher version]:
if(sqlite3_exec(db, (const char*)"SELECT count(*) FROM sqlite_master;", NULL, NULL, NULL) == SQLITE_OK) {
NSLog(@"DB open, check sqlite master table OK");
dbPointer = [NSValue valueWithPointer:db];
[openDBs setObject: dbPointer forKey: dbfilename];

Boolean databaseCheck = NO;
if ([self checkDatabaseConnection: db dbfilename: dbfilename]) {
databaseCheck = YES;
} else {
NSString *sCipherMigrate = [options objectForKey:@"cipherMigrate"];
Boolean cipherMigrate = sCipherMigrate ? [sCipherMigrate boolValue] : NO;

if (cipherMigrate) {
db = [self executeCipherMigration:db name:name options:options];
if (db != NULL) {
databaseCheck = [self checkDatabaseConnection: db dbfilename: dbfilename];
}
}
}

if (databaseCheck) {
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:@"Database opened"];
} else {
NSLog(@"ERROR reading sqlite master table");
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Unable to open DB with key"];
// XXX TODO: close the db handle & [perhaps] remove from openDBs!!
}
}
}
Expand All @@ -216,6 +215,97 @@ -(void)openNow: (CDVInvokedUrlCommand*)command
// DLog(@"open cb finished ok");
}

-(Boolean) prepareDatabase: (sqlite3*) db options: (NSMutableDictionary*) options
{
sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, 1, NULL);

#if 0
sqlite3_create_function(db, "REGEXP", 2, SQLITE_ANY, NULL, &sqlite_regexp, NULL, NULL);
#endif

// SQLCipher key:
NSString *dbkey = [options objectForKey:@"key"];
const char *key = NULL;
if (dbkey != NULL && dbkey.length != 0) key = [dbkey UTF8String];
NSLog((key != NULL) ? @"Open DB with encryption" : @"Open DB with NO encryption");
if (key != NULL) {
if (sqlite3_key(db, key, strlen(key)) != SQLITE_OK) {
[self logSqlError: db message: @"Error setting key: "];
return NO;
}
}

return YES;
}

-(sqlite3*) reopenDatabase: (sqlite3*) db name: (const char *) name options: (NSMutableDictionary*) options
{
sqlite3_close(db);

sqlite3* newDb;

if (sqlite3_open(name, &newDb) != SQLITE_OK) {
[self logSqlError:db message:@"Unable to open DB"];
return NULL;
} else {
[self prepareDatabase:newDb options:options];
return newDb;
}
}

-(sqlite3*) executeCipherMigration: (sqlite3*) db name: (const char *) name options: (NSMutableDictionary*) options
{
db = [self reopenDatabase:db name:name options:options];
if (db == NULL) {
NSLog(@"%@", @"Unable to reopen database for cipher migration");
return NULL;
} else {
sqlite3_stmt *statement;

sqlite3_prepare_v2(db, "PRAGMA CIPHER_MIGRATE", -1, &statement, NULL);

while (sqlite3_step(statement) == SQLITE_ROW) {
NSString *result = [[NSString alloc] initWithUTF8String:
(const char *) sqlite3_column_text(statement, 0)];
NSLog(@"%@%@", @"Pragma migrate: ", result);
}

if (sqlite3_finalize(statement) != SQLITE_OK) {
[self logSqlError:db message:@"Unable to finalize cipher migrate statement: "];
return NULL;
}

db = [self reopenDatabase:db name:name options:options];
if (db == NULL) {
NSLog(@"%@", @"Unable to reopen database after cipher migration");
return NULL;
} else {
return db;
}
}
}

-(void) logSqlError: (sqlite3*)db message:(NSString*) message
{
const char *errmsg = sqlite3_errmsg(db);
NSLog(@"%@%@", message, [NSString stringWithUTF8String:errmsg]);
}

-(Boolean) checkDatabaseConnection: (sqlite3*)db dbfilename: (NSString*) dbfilename
{
int checkResult = sqlite3_exec(db, (const char*)"SELECT count(*) FROM sqlite_master;", NULL, NULL, NULL);

if (checkResult == SQLITE_OK) {
NSLog(@"DB open, check sqlite master table OK");
NSValue *dbPointer = [NSValue valueWithPointer:db];
[openDBs setObject: dbPointer forKey: dbfilename];
return YES;
} else {
[self logSqlError: db message: @"Error checking connection: "];
return NO;
}
}

-(void) close: (CDVInvokedUrlCommand*)command
{
[self.commandDelegate runInBackground:^{
Expand Down