diff --git a/src/android/io/sqlc/SQLiteAndroidDatabase.java b/src/android/io/sqlc/SQLiteAndroidDatabase.java index 4be412ad..b5d5aa91 100644 --- a/src/android/io/sqlc/SQLiteAndroidDatabase.java +++ b/src/android/io/sqlc/SQLiteAndroidDatabase.java @@ -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). */ diff --git a/src/android/io/sqlc/SQLitePlugin.java b/src/android/io/sqlc/SQLitePlugin.java index c468c59f..43c77c2e 100755 --- a/src/android/io/sqlc/SQLitePlugin.java +++ b/src/android/io/sqlc/SQLitePlugin.java @@ -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()] @@ -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(); @@ -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 q; final CallbackContext openCbc; @@ -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(); 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); diff --git a/src/ios/SQLitePlugin.m b/src/ios/SQLitePlugin.m index b3a8315a..448f43c8 100755 --- a/src/ios/SQLitePlugin.m +++ b/src/ios/SQLitePlugin.m @@ -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!! } } } @@ -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:^{