mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-23 02:10:44 +01:00
Add an encrypted key-value store.
SignalStore is backed by SQLCipher and is intended to be used instead of TextSecurePreferences moving forward.
This commit is contained in:
@@ -60,6 +60,7 @@ public class DatabaseFactory {
|
||||
private final JobDatabase jobDatabase;
|
||||
private final StickerDatabase stickerDatabase;
|
||||
private final StorageKeyDatabase storageKeyDatabase;
|
||||
private final KeyValueDatabase keyValueDatabase;
|
||||
|
||||
public static DatabaseFactory getInstance(Context context) {
|
||||
synchronized (lock) {
|
||||
@@ -150,6 +151,10 @@ public class DatabaseFactory {
|
||||
return getInstance(context).storageKeyDatabase;
|
||||
}
|
||||
|
||||
public static KeyValueDatabase getKeyValueDatabase(Context context) {
|
||||
return getInstance(context).keyValueDatabase;
|
||||
}
|
||||
|
||||
public static SQLiteDatabase getBackupDatabase(Context context) {
|
||||
return getInstance(context).databaseHelper.getReadableDatabase();
|
||||
}
|
||||
@@ -187,6 +192,7 @@ public class DatabaseFactory {
|
||||
this.jobDatabase = new JobDatabase(context, databaseHelper);
|
||||
this.stickerDatabase = new StickerDatabase(context, databaseHelper, attachmentSecret);
|
||||
this.storageKeyDatabase = new StorageKeyDatabase(context, databaseHelper);
|
||||
this.keyValueDatabase = new KeyValueDatabase(context, databaseHelper);
|
||||
}
|
||||
|
||||
public void onApplicationLevelUpgrade(@NonNull Context context, @NonNull MasterSecret masterSecret,
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.keyvalue.KeyValueDataSet;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
public class KeyValueDatabase extends Database {
|
||||
|
||||
private static final String TABLE_NAME = "key_value";
|
||||
|
||||
private static final String ID = "_id";
|
||||
private static final String KEY = "key";
|
||||
private static final String VALUE = "value";
|
||||
private static final String TYPE = "type";
|
||||
|
||||
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + "(" + ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
||||
KEY + " TEXT UNIQUE, " +
|
||||
VALUE + " TEXT, " +
|
||||
TYPE + " INTEGER)";
|
||||
|
||||
KeyValueDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
|
||||
super(context, databaseHelper);
|
||||
}
|
||||
|
||||
public @NonNull KeyValueDataSet getDataSet() {
|
||||
KeyValueDataSet dataSet = new KeyValueDataSet();
|
||||
|
||||
try (Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, null, null, null, null, null)){
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
Type type = Type.fromId(cursor.getInt(cursor.getColumnIndexOrThrow(TYPE)));
|
||||
String key = cursor.getString(cursor.getColumnIndexOrThrow(KEY));
|
||||
|
||||
switch (type) {
|
||||
case BLOB:
|
||||
dataSet.putBlob(key, cursor.getBlob(cursor.getColumnIndexOrThrow(VALUE)));
|
||||
break;
|
||||
case BOOLEAN:
|
||||
dataSet.putBoolean(key, cursor.getInt(cursor.getColumnIndexOrThrow(VALUE)) == 1);
|
||||
break;
|
||||
case FLOAT:
|
||||
dataSet.putFloat(key, cursor.getFloat(cursor.getColumnIndexOrThrow(VALUE)));
|
||||
break;
|
||||
case INTEGER:
|
||||
dataSet.putInteger(key, cursor.getInt(cursor.getColumnIndexOrThrow(VALUE)));
|
||||
break;
|
||||
case LONG:
|
||||
dataSet.putLong(key, cursor.getLong(cursor.getColumnIndexOrThrow(VALUE)));
|
||||
break;
|
||||
case STRING:
|
||||
dataSet.putString(key, cursor.getString(cursor.getColumnIndexOrThrow(VALUE)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dataSet;
|
||||
}
|
||||
|
||||
public void writeDataSet(@NonNull KeyValueDataSet dataSet, @NonNull Collection<String> removes) {
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
|
||||
db.beginTransaction();
|
||||
try {
|
||||
for (Map.Entry<String, Object> entry : dataSet.getValues().entrySet()) {
|
||||
String key = entry.getKey();
|
||||
Object value = entry.getValue();
|
||||
Class type = dataSet.getType(key);
|
||||
|
||||
ContentValues contentValues = new ContentValues(3);
|
||||
contentValues.put(KEY, key);
|
||||
|
||||
if (type == byte[].class) {
|
||||
contentValues.put(VALUE, (byte[]) value);
|
||||
contentValues.put(TYPE, Type.BLOB.getId());
|
||||
} else if (type == Boolean.class) {
|
||||
contentValues.put(VALUE, (boolean) value);
|
||||
contentValues.put(TYPE, Type.BOOLEAN.getId());
|
||||
} else if (type == Float.class) {
|
||||
contentValues.put(VALUE, (float) value);
|
||||
contentValues.put(TYPE, Type.FLOAT.getId());
|
||||
} else if (type == Integer.class) {
|
||||
contentValues.put(VALUE, (int) value);
|
||||
contentValues.put(TYPE, Type.INTEGER.getId());
|
||||
} else if (type == Long.class) {
|
||||
contentValues.put(VALUE, (long) value);
|
||||
contentValues.put(TYPE, Type.LONG.getId());
|
||||
} else if (type == String.class) {
|
||||
contentValues.put(VALUE, (String) value);
|
||||
contentValues.put(TYPE, Type.STRING.getId());
|
||||
} else {
|
||||
throw new AssertionError("Unknown type: " + type);
|
||||
}
|
||||
|
||||
db.insertWithOnConflict(TABLE_NAME, null, contentValues, SQLiteDatabase.CONFLICT_REPLACE);
|
||||
}
|
||||
|
||||
String deleteQuery = KEY + " = ?";
|
||||
for (String remove : removes) {
|
||||
db.delete(TABLE_NAME, deleteQuery, new String[] { remove });
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
private enum Type {
|
||||
BLOB(0), BOOLEAN(1), FLOAT(2), INTEGER(3), LONG(4), STRING(5);
|
||||
|
||||
final int id;
|
||||
|
||||
Type(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public static Type fromId(int id) {
|
||||
return values()[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.database.GroupReceiptDatabase;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.database.JobDatabase;
|
||||
import org.thoughtcrime.securesms.database.KeyValueDatabase;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.OneTimePreKeyDatabase;
|
||||
import org.thoughtcrime.securesms.database.PushDatabase;
|
||||
@@ -99,8 +100,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
private static final int STORAGE_SERVICE = 38;
|
||||
private static final int REACTIONS_UNREAD_INDEX = 39;
|
||||
private static final int RESUMABLE_DOWNLOADS = 40;
|
||||
private static final int KEY_VALUE_STORE = 41;
|
||||
|
||||
private static final int DATABASE_VERSION = 40;
|
||||
private static final int DATABASE_VERSION = 41;
|
||||
private static final String DATABASE_NAME = "signal.db";
|
||||
|
||||
private final Context context;
|
||||
@@ -142,6 +144,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
db.execSQL(SessionDatabase.CREATE_TABLE);
|
||||
db.execSQL(StickerDatabase.CREATE_TABLE);
|
||||
db.execSQL(StorageKeyDatabase.CREATE_TABLE);
|
||||
db.execSQL(KeyValueDatabase.CREATE_TABLE);
|
||||
executeStatements(db, SearchDatabase.CREATE_TABLE);
|
||||
executeStatements(db, JobDatabase.CREATE_TABLE);
|
||||
|
||||
@@ -684,6 +687,13 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
db.execSQL("ALTER TABLE part ADD COLUMN transfer_file TEXT DEFAULT NULL");
|
||||
}
|
||||
|
||||
if (oldVersion < KEY_VALUE_STORE) {
|
||||
db.execSQL("CREATE TABLE key_value (_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
||||
"key TEXT UNIQUE, " +
|
||||
"value TEXT, " +
|
||||
"type INTEGER)");
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
|
||||
Reference in New Issue
Block a user