Search code examples
c#sqlitexamarinsqlciphercouchbase-lite

Error while storing Couchbase Lite database in a custom directory while implementing SQLCipher


So,

What i want:

To be able to tell Couchbase Lite (CB Lite) where to store the data and implement SQLCipher encryption on it.

What is my problem:

Even though i have the encryption working on the default directory, as soon as i tell the Manager to use a custom directory it throws an exception when trying to create the database: Message:

"Error 8, 1032 (attempt to write a readonly database) executing sql ' CREATE INDEX docs_docid ON docs(docid)'"

Source:

"Couchbase.Lite.Storage.SQLCipher"

StackTrace:

"at Couchbase.Lite.Storage.SQLCipher.SqlitePCLRawStorageEngine.ExecSQL (System.String sql, SQLitePCL.sqlite3 db, System.Object[] paramArgs) [0x00079] in /Users/jenkins/jenkins/workspace/couchbase-lite-net-build@2/1.4/Android/couchbase-lite-net/src/StorageEngines/SQLiteCommon/storage.sqlite.common/src/DatabaseUpgraderFactory_Upgraders.cs:249 \n at Couchbase.Lite.Storage.SQLCipher.SqlitePCLRawStorageEngine.ExecSQL (System.String sql, System.Object[] paramArgs) <0xd70821f8 + 0x0005f> in <223c4357c0d44faaaa01c387793a30cc>:0 \n at Couchbase.Lite.Storage.SQLCipher.SqlitePCLRawStorageEngine.Open (System.String path, System.Boolean readOnly, System.String schema, Couchbase.Lite.Store.SymmetricKey encryptionKey) [0x00065] in /Users/jenkins/jenkins/workspace/couchbase-lite-net-build@2/1.4/Android/couchbase-lite-net/src/StorageEngines/SQLiteCommon/storage.sqlite.common/src/DatabaseUpgraderFactory.cs:87 "

Now, The strange thing is that when running my code on the emulator, it throws no exception. The problem comes only when running my code on a real device (Lenovo TAB 2A Series, Android 5.0.1).

Note: Even though the exception is thrown the custom folder is created, and the couchbase lite folder as well, but inside it is only to be found a file called db.sqlite3. Normally three files and a folder are created: enter image description here

Note 2: I had added the WRITE_EXTERNAL_STORAGE permission to the Android Manifest.

The code that runs with no problem:

public async Task<Database> OpenDatabaseOnEncryptedWayAsync(string encryptionPassword)
    {
        Database encryptedDatabase = null;

        try
        {
            SymmetricKey Key;
            DatabaseOptions options = new DatabaseOptions();
            await Task.Run(() =>
            {
                Key = new SymmetricKey(encryptionPassword);
                options.EncryptionKey = Key;
                options.Create = true;
                options.StorageType = StorageEngineTypes.SQLite;
            });

            // Database stored in default directory
            encryptedDatabase = Manager.SharedInstance.OpenDatabase(database, options);
        }
        catch (CouchbaseLiteException e)
        {
            LogWriterMethod.TryWriteLog(String.Format("Error while creating encrypted database: {0}", e)); 
            Console.WriteLine("Encryption Message: {0}", e);
        }
        catch (Exception e)
        {
            LogWriterMethod.TryWriteLog(String.Format("Unhandled Exception on OpenDatabaseOnEncryptedWayAsync method: {0}", e));
            Console.WriteLine("Unhandled Exception Message: {0}", e);
        }

        App.SettingsViewModel.isDatabaseOpen = encryptedDatabase != null;

        return encryptedDatabase;
    }

The code that throws the (CouchbaseLite)exception:

    public async Task<Database> OpenDatabaseOnEncryptedWayAsync(string encryptionPassword)
    {            
        Database encryptedDatabase = null;

        try
        {
            SymmetricKey Key;
            DatabaseOptions options = new DatabaseOptions();
            await Task.Run(() =>
            {
                Key = new SymmetricKey(encryptionPassword);
                options.EncryptionKey = Key;
                options.Create = true;
                options.StorageType = StorageEngineTypes.SQLite;
            });

            // Custom directory
            var path = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads).Path;
            var dir = Directory.CreateDirectory(path);
            Manager manager = new Manager(dir, Manager.DefaultOptions);
            encryptedDatabase = manager.GetDatabase(database);
        }
        catch (CouchbaseLiteException e)
        {
            LogWriterMethod.TryWriteLog(String.Format("Error while creating encrypted database: {0}", e)); 
            Console.WriteLine("Encryption Message: {0}", e);
        }
        catch (Exception e)
        {
            LogWriterMethod.TryWriteLog(String.Format("Unhandled Exception on OpenDatabaseOnEncryptedWayAsync method: {0}", e));
            Console.WriteLine("Unhandled Exception Message: {0}", e);
        }

        App.SettingsViewModel.isDatabaseOpen = encryptedDatabase != null;

        return encryptedDatabase;
    }

So, does anybody out there knows how to deal with this issue? Thanks in advance :)


Solution

  • Comming back to the topic, i found a very simple error in the code that throws the (CouchbaseLite)Exception. I share it in case anyone reading this falls in the same error:

    I am creating the directory and then the manager using the new path, but i never assign the DatabaseOptions to the newly created Manager

    SymmetricKey Key;
    DatabaseOptions options = new DatabaseOptions();
    await Task.Run(() =>
    {
        Key = new SymmetricKey(encryptionPassword);
        options.EncryptionKey = Key;
        options.Create = true;
        options.StorageType = StorageEngineTypes.SQLite;
    });
    var path = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads).Path;
    var dir = Directory.CreateDirectory(path);
    Manager manager = new Manager(dir, Manager.DefaultOptions);
    encryptedDatabase = manager.GetDatabase(database);
    

    So, all we have to do is to change the code to read

    encryptedDatabase = manager.OpenDatabase(database, options);
    

    And that's it!