/* _____ _ * |_ _| |_ _ _ ___ ___ _ __ __ _ * | | | ' \| '_/ -_) -_) ' \/ _` |_ * |_| |_||_|_| \___\___|_|_|_\__,_(_) * * Threema for Android * Copyright (c) 2015-2023 Threema GmbH * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ package ch.threema.storage; import android.content.Context; import android.text.format.DateUtils; import android.widget.Toast; import androidx.annotation.MainThread; import net.sqlcipher.database.SQLiteDatabase; import net.sqlcipher.database.SQLiteDatabaseHook; import net.sqlcipher.database.SQLiteException; import net.sqlcipher.database.SQLiteOpenHelper; import org.slf4j.Logger; import java.io.File; import java.io.IOException; import androidx.annotation.NonNull; import ch.threema.app.exceptions.DatabaseMigrationFailedException; import ch.threema.app.exceptions.DatabaseMigrationLockedException; import ch.threema.app.services.UpdateSystemService; import ch.threema.app.services.systemupdate.SystemUpdateToVersion10; import ch.threema.app.services.systemupdate.SystemUpdateToVersion11; import ch.threema.app.services.systemupdate.SystemUpdateToVersion12; import ch.threema.app.services.systemupdate.SystemUpdateToVersion13; import ch.threema.app.services.systemupdate.SystemUpdateToVersion14; import ch.threema.app.services.systemupdate.SystemUpdateToVersion15; import ch.threema.app.services.systemupdate.SystemUpdateToVersion16; import ch.threema.app.services.systemupdate.SystemUpdateToVersion17; import ch.threema.app.services.systemupdate.SystemUpdateToVersion19; import ch.threema.app.services.systemupdate.SystemUpdateToVersion20; import ch.threema.app.services.systemupdate.SystemUpdateToVersion21; import ch.threema.app.services.systemupdate.SystemUpdateToVersion24; import ch.threema.app.services.systemupdate.SystemUpdateToVersion25; import ch.threema.app.services.systemupdate.SystemUpdateToVersion27; import ch.threema.app.services.systemupdate.SystemUpdateToVersion28; import ch.threema.app.services.systemupdate.SystemUpdateToVersion31; import ch.threema.app.services.systemupdate.SystemUpdateToVersion32; import ch.threema.app.services.systemupdate.SystemUpdateToVersion33; import ch.threema.app.services.systemupdate.SystemUpdateToVersion34; import ch.threema.app.services.systemupdate.SystemUpdateToVersion35; import ch.threema.app.services.systemupdate.SystemUpdateToVersion36; import ch.threema.app.services.systemupdate.SystemUpdateToVersion37; import ch.threema.app.services.systemupdate.SystemUpdateToVersion38; import ch.threema.app.services.systemupdate.SystemUpdateToVersion39; import ch.threema.app.services.systemupdate.SystemUpdateToVersion4; import ch.threema.app.services.systemupdate.SystemUpdateToVersion40; import ch.threema.app.services.systemupdate.SystemUpdateToVersion41; import ch.threema.app.services.systemupdate.SystemUpdateToVersion42; import ch.threema.app.services.systemupdate.SystemUpdateToVersion43; import ch.threema.app.services.systemupdate.SystemUpdateToVersion44; import ch.threema.app.services.systemupdate.SystemUpdateToVersion45; import ch.threema.app.services.systemupdate.SystemUpdateToVersion46; import ch.threema.app.services.systemupdate.SystemUpdateToVersion47; import ch.threema.app.services.systemupdate.SystemUpdateToVersion48; import ch.threema.app.services.systemupdate.SystemUpdateToVersion49; import ch.threema.app.services.systemupdate.SystemUpdateToVersion50; import ch.threema.app.services.systemupdate.SystemUpdateToVersion51; import ch.threema.app.services.systemupdate.SystemUpdateToVersion52; import ch.threema.app.services.systemupdate.SystemUpdateToVersion53; import ch.threema.app.services.systemupdate.SystemUpdateToVersion54; import ch.threema.app.services.systemupdate.SystemUpdateToVersion55; import ch.threema.app.services.systemupdate.SystemUpdateToVersion56; import ch.threema.app.services.systemupdate.SystemUpdateToVersion58; import ch.threema.app.services.systemupdate.SystemUpdateToVersion59; import ch.threema.app.services.systemupdate.SystemUpdateToVersion6; import ch.threema.app.services.systemupdate.SystemUpdateToVersion60; import ch.threema.app.services.systemupdate.SystemUpdateToVersion61; import ch.threema.app.services.systemupdate.SystemUpdateToVersion62; import ch.threema.app.services.systemupdate.SystemUpdateToVersion63; import ch.threema.app.services.systemupdate.SystemUpdateToVersion64; import ch.threema.app.services.systemupdate.SystemUpdateToVersion65; import ch.threema.app.services.systemupdate.SystemUpdateToVersion66; import ch.threema.app.services.systemupdate.SystemUpdateToVersion67; import ch.threema.app.services.systemupdate.SystemUpdateToVersion68; import ch.threema.app.services.systemupdate.SystemUpdateToVersion69; import ch.threema.app.services.systemupdate.SystemUpdateToVersion7; import ch.threema.app.services.systemupdate.SystemUpdateToVersion70; import ch.threema.app.services.systemupdate.SystemUpdateToVersion71; import ch.threema.app.services.systemupdate.SystemUpdateToVersion72; import ch.threema.app.services.systemupdate.SystemUpdateToVersion73; import ch.threema.app.services.systemupdate.SystemUpdateToVersion74; import ch.threema.app.services.systemupdate.SystemUpdateToVersion75; import ch.threema.app.services.systemupdate.SystemUpdateToVersion76; import ch.threema.app.services.systemupdate.SystemUpdateToVersion77; import ch.threema.app.services.systemupdate.SystemUpdateToVersion78; import ch.threema.app.services.systemupdate.SystemUpdateToVersion79; import ch.threema.app.services.systemupdate.SystemUpdateToVersion8; import ch.threema.app.services.systemupdate.SystemUpdateToVersion80; import ch.threema.app.services.systemupdate.SystemUpdateToVersion81; import ch.threema.app.services.systemupdate.SystemUpdateToVersion9; import ch.threema.app.utils.FileUtil; import ch.threema.app.utils.RuntimeUtil; import ch.threema.app.utils.TestUtil; import ch.threema.base.utils.LoggingUtil; import ch.threema.storage.factories.BallotChoiceModelFactory; import ch.threema.storage.factories.BallotModelFactory; import ch.threema.storage.factories.BallotVoteModelFactory; import ch.threema.storage.factories.ContactModelFactory; import ch.threema.storage.factories.ConversationTagFactory; import ch.threema.storage.factories.DistributionListMemberModelFactory; import ch.threema.storage.factories.DistributionListMessageModelFactory; import ch.threema.storage.factories.DistributionListModelFactory; import ch.threema.storage.factories.GroupBallotModelFactory; import ch.threema.storage.factories.GroupCallModelFactory; import ch.threema.storage.factories.GroupInviteModelFactory; import ch.threema.storage.factories.GroupMemberModelFactory; import ch.threema.storage.factories.GroupMessageModelFactory; import ch.threema.storage.factories.GroupMessagePendingMessageIdModelFactory; import ch.threema.storage.factories.GroupModelFactory; import ch.threema.storage.factories.GroupRequestSyncLogModelFactory; import ch.threema.storage.factories.IdentityBallotModelFactory; import ch.threema.storage.factories.IncomingGroupJoinRequestModelFactory; import ch.threema.storage.factories.MessageModelFactory; import ch.threema.storage.factories.ModelFactory; import ch.threema.storage.factories.OutgoingGroupJoinRequestModelFactory; import ch.threema.storage.factories.ServerMessageModelFactory; import ch.threema.storage.factories.WebClientSessionModelFactory; public class DatabaseServiceNew extends SQLiteOpenHelper { private static final Logger logger = LoggingUtil.getThreemaLogger("DatabaseServiceNew"); public static final String DATABASE_NAME = "threema.db"; public static final String DATABASE_NAME_V4 = "threema4.db"; public static final String DATABASE_BACKUP_EXT = ".backup"; private static final int DATABASE_VERSION = SystemUpdateToVersion81.VERSION; private final Context context; private final String key; private final UpdateSystemService updateSystemService; private ContactModelFactory contactModelFactory; private MessageModelFactory messageModelFactory; private GroupModelFactory groupModelFactory; private GroupMemberModelFactory groupMemberModelFactory; private GroupMessageModelFactory groupMessageModelFactory; private DistributionListModelFactory distributionListModelFactory; private DistributionListMemberModelFactory distributionListMemberModelFactory; private DistributionListMessageModelFactory distributionListMessageModelFactory; private GroupRequestSyncLogModelFactory groupRequestSyncLogModelFactory; private BallotModelFactory ballotModelFactory; private BallotChoiceModelFactory ballotChoiceModelFactory; private BallotVoteModelFactory ballotVoteModelFactory; private IdentityBallotModelFactory identityBallotModelFactory; private GroupBallotModelFactory groupBallotModelFactory; private GroupMessagePendingMessageIdModelFactory groupMessagePendingMessageIdModelFactory; private WebClientSessionModelFactory webClientSessionModelFactory; private ConversationTagFactory conversationTagFactory; private GroupInviteModelFactory groupInviteModelFactory; private OutgoingGroupJoinRequestModelFactory outgoingGroupJoinRequestModelFactory; private IncomingGroupJoinRequestModelFactory incomingGroupJoinRequestModelFactory; private GroupCallModelFactory groupCallModelFactory; private ServerMessageModelFactory serverMessageModelFactory; public DatabaseServiceNew(final Context context, final String databaseKey, UpdateSystemService updateSystemService, int sqlcipherVersion) { super( context, sqlcipherVersion == 4 ? DATABASE_NAME_V4 : DATABASE_NAME, null, DATABASE_VERSION, new SQLiteDatabaseHook() { @Override public void preKey(SQLiteDatabase sqLiteDatabase) { if (sqlcipherVersion == 4) { sqLiteDatabase.rawExecSQL("PRAGMA cipher_default_kdf_iter = 1;"); } else { sqLiteDatabase.rawExecSQL( "PRAGMA cipher_default_page_size = 1024;" + "PRAGMA cipher_default_kdf_iter = 4000;" + "PRAGMA cipher_default_hmac_algorithm = HMAC_SHA1;" + "PRAGMA cipher_default_kdf_algorithm = PBKDF2_HMAC_SHA1;"); } } @Override public void postKey(SQLiteDatabase sqLiteDatabase) { if (sqlcipherVersion == 4) { sqLiteDatabase.rawExecSQL("PRAGMA kdf_iter = 1;"); // turn off memory wiping for now due to https://github.com/sqlcipher/android-database-sqlcipher/issues/411 sqLiteDatabase.rawExecSQL("PRAGMA cipher_memory_security = OFF;"); } else { sqLiteDatabase.rawExecSQL( "PRAGMA cipher_page_size = 1024;" + "PRAGMA kdf_iter = 4000;" + "PRAGMA cipher_hmac_algorithm = HMAC_SHA1;" + "PRAGMA cipher_kdf_algorithm = PBKDF2_HMAC_SHA1;"); } } } , sqLiteDatabase -> { logger.error("Database corrupted"); RuntimeUtil.runOnUiThread(() -> { if (context != null) { Toast.makeText(context, "Database corrupted. Please save all data!", Toast.LENGTH_LONG).show(); } }); // close database if (sqLiteDatabase.isOpen()) { try { sqLiteDatabase.close(); } catch (Exception e) { logger.error("Exception while closing database", e); } } System.exit(2); } ); logger.info("instantiated"); this.updateSystemService = updateSystemService; this.context = context; SQLiteDatabase.loadLibs(context); this.key = databaseKey; } public synchronized SQLiteDatabase getWritableDatabase() throws SQLiteException { return super.getWritableDatabase(this.key); } public synchronized SQLiteDatabase getReadableDatabase() { return super.getReadableDatabase(this.key); } @Override public void onCreate(SQLiteDatabase sqLiteDatabase) { for(ModelFactory f: new ModelFactory[] { this.getContactModelFactory(), this.getMessageModelFactory(), this.getGroupModelFactory(), this.getGroupMemberModelFactory(), this.getGroupMessageModelFactory(), this.getDistributionListModelFactory(), this.getDistributionListMemberModelFactory(), this.getDistributionListMessageModelFactory(), this.getGroupRequestSyncLogModelFactory(), this.getBallotModelFactory(), this.getBallotChoiceModelFactory(), this.getBallotVoteModelFactory(), this.getIdentityBallotModelFactory(), this.getGroupBallotModelFactory(), this.getGroupMessagePendingMessageIdModelFactory(), this.getWebClientSessionModelFactory(), this.getConversationTagFactory(), this.getGroupInviteModelFactory(), this.getIncomingGroupJoinRequestModelFactory(), this.getOutgoingGroupJoinRequestModelFactory(), this.getGroupCallModelFactory(), this.getServerMessageModelFactory(), }) { String[] createTableStatement = f.getStatements(); if(createTableStatement != null) { for (String statement : createTableStatement) { if (!TestUtil.empty(statement)) { sqLiteDatabase.execSQL(statement); } } } } } @NonNull public ContactModelFactory getContactModelFactory() { if(this.contactModelFactory == null) { this.contactModelFactory = new ContactModelFactory(this); } return this.contactModelFactory; } @NonNull public MessageModelFactory getMessageModelFactory() { if(this.messageModelFactory == null) { this.messageModelFactory = new MessageModelFactory(this); } return this.messageModelFactory; } @NonNull public GroupModelFactory getGroupModelFactory() { if(this.groupModelFactory == null) { this.groupModelFactory = new GroupModelFactory(this); } return this.groupModelFactory; } @NonNull public GroupMemberModelFactory getGroupMemberModelFactory() { if(this.groupMemberModelFactory == null) { this.groupMemberModelFactory = new GroupMemberModelFactory(this); } return this.groupMemberModelFactory; } @NonNull public GroupMessageModelFactory getGroupMessageModelFactory() { if(this.groupMessageModelFactory == null) { this.groupMessageModelFactory = new GroupMessageModelFactory(this); } return this.groupMessageModelFactory; } @NonNull public DistributionListModelFactory getDistributionListModelFactory() { if(this.distributionListModelFactory == null) { this.distributionListModelFactory = new DistributionListModelFactory(this); } return this.distributionListModelFactory; } @NonNull public DistributionListMemberModelFactory getDistributionListMemberModelFactory() { if(this.distributionListMemberModelFactory == null) { this.distributionListMemberModelFactory = new DistributionListMemberModelFactory(this); } return this.distributionListMemberModelFactory; } @NonNull public DistributionListMessageModelFactory getDistributionListMessageModelFactory() { if(this.distributionListMessageModelFactory == null) { this.distributionListMessageModelFactory = new DistributionListMessageModelFactory(this); } return this.distributionListMessageModelFactory; } @NonNull public GroupRequestSyncLogModelFactory getGroupRequestSyncLogModelFactory() { if(this.groupRequestSyncLogModelFactory == null) { this.groupRequestSyncLogModelFactory = new GroupRequestSyncLogModelFactory(this); } return this.groupRequestSyncLogModelFactory; } @NonNull public BallotModelFactory getBallotModelFactory() { if(this.ballotModelFactory == null) { this.ballotModelFactory = new BallotModelFactory(this); } return this.ballotModelFactory; } @NonNull public BallotChoiceModelFactory getBallotChoiceModelFactory() { if (this.ballotChoiceModelFactory == null) { this.ballotChoiceModelFactory = new BallotChoiceModelFactory(this); } return this.ballotChoiceModelFactory; } @NonNull public BallotVoteModelFactory getBallotVoteModelFactory() { if(this.ballotVoteModelFactory == null) { this.ballotVoteModelFactory = new BallotVoteModelFactory(this); } return this.ballotVoteModelFactory; } @NonNull public IdentityBallotModelFactory getIdentityBallotModelFactory() { if(this.identityBallotModelFactory == null) { this.identityBallotModelFactory = new IdentityBallotModelFactory(this); } return this.identityBallotModelFactory; } @NonNull public GroupBallotModelFactory getGroupBallotModelFactory() { if(this.groupBallotModelFactory == null) { this.groupBallotModelFactory = new GroupBallotModelFactory(this); } return this.groupBallotModelFactory; } @NonNull public GroupMessagePendingMessageIdModelFactory getGroupMessagePendingMessageIdModelFactory() { if(this.groupMessagePendingMessageIdModelFactory == null) { this.groupMessagePendingMessageIdModelFactory = new GroupMessagePendingMessageIdModelFactory(this); } return this.groupMessagePendingMessageIdModelFactory; } @NonNull public WebClientSessionModelFactory getWebClientSessionModelFactory() { if(this.webClientSessionModelFactory == null) { this.webClientSessionModelFactory = new WebClientSessionModelFactory(this); } return this.webClientSessionModelFactory; } @NonNull public ConversationTagFactory getConversationTagFactory() { if(this.conversationTagFactory == null) { this.conversationTagFactory = new ConversationTagFactory(this); } return this.conversationTagFactory; } @NonNull public GroupInviteModelFactory getGroupInviteModelFactory() { if(this.groupInviteModelFactory == null) { this.groupInviteModelFactory = new GroupInviteModelFactory(this); } return this.groupInviteModelFactory; } @NonNull public IncomingGroupJoinRequestModelFactory getIncomingGroupJoinRequestModelFactory() { if (this.incomingGroupJoinRequestModelFactory == null) { this.incomingGroupJoinRequestModelFactory = new IncomingGroupJoinRequestModelFactory(this); } return this.incomingGroupJoinRequestModelFactory; } @NonNull public OutgoingGroupJoinRequestModelFactory getOutgoingGroupJoinRequestModelFactory() { if (this.outgoingGroupJoinRequestModelFactory == null) { this.outgoingGroupJoinRequestModelFactory = new OutgoingGroupJoinRequestModelFactory(this); } return this.outgoingGroupJoinRequestModelFactory; } @NonNull public GroupCallModelFactory getGroupCallModelFactory() { if (this.groupCallModelFactory == null) { this.groupCallModelFactory = new GroupCallModelFactory(this); } return this.groupCallModelFactory; } @NonNull public ServerMessageModelFactory getServerMessageModelFactory() { if (this.serverMessageModelFactory == null) { this.serverMessageModelFactory = new ServerMessageModelFactory(this); } return this.serverMessageModelFactory; } // Note: Enable this to allow database downgrades. // //@Override //public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { // logger.info("onDowngrade, version {} -> {}", oldVersion, newVersion); //} @Override public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) { logger.info("onUpgrade, version {} -> {}", oldVersion, newVersion); if (oldVersion < 4) { this.updateSystemService.addUpdate(new SystemUpdateToVersion4(sqLiteDatabase)); } if (oldVersion < 6) { this.updateSystemService.addUpdate(new SystemUpdateToVersion6(this.context, sqLiteDatabase)); } if (oldVersion < 7) { this.updateSystemService.addUpdate(new SystemUpdateToVersion7(sqLiteDatabase)); } if (oldVersion < 8) { this.updateSystemService.addUpdate(new SystemUpdateToVersion8(this, sqLiteDatabase)); } if (oldVersion < 9) { this.updateSystemService.addUpdate(new SystemUpdateToVersion9(sqLiteDatabase)); } if (oldVersion < 10) { this.updateSystemService.addUpdate(new SystemUpdateToVersion10(sqLiteDatabase)); } if (oldVersion < 11) { this.updateSystemService.addUpdate(new SystemUpdateToVersion11(sqLiteDatabase)); } if (oldVersion < 12) { this.updateSystemService.addUpdate(new SystemUpdateToVersion12(this.context, sqLiteDatabase)); } if (oldVersion < 13) { this.updateSystemService.addUpdate(new SystemUpdateToVersion13(sqLiteDatabase)); } if (oldVersion < 14) { this.updateSystemService.addUpdate(new SystemUpdateToVersion14()); } if (oldVersion < 15) { this.updateSystemService.addUpdate(new SystemUpdateToVersion15(this, sqLiteDatabase)); } if (oldVersion < 16) { this.updateSystemService.addUpdate(new SystemUpdateToVersion16(sqLiteDatabase)); } if (oldVersion < 17) { this.updateSystemService.addUpdate(new SystemUpdateToVersion17(sqLiteDatabase)); } if (oldVersion < 19) { this.updateSystemService.addUpdate(new SystemUpdateToVersion19(sqLiteDatabase)); } if (oldVersion < 20) { this.updateSystemService.addUpdate(new SystemUpdateToVersion20(sqLiteDatabase)); } if (oldVersion < 21) { this.updateSystemService.addUpdate(new SystemUpdateToVersion21(this, sqLiteDatabase)); } if (oldVersion < 24) { this.updateSystemService.addUpdate(new SystemUpdateToVersion24(sqLiteDatabase)); } if (oldVersion < 25) { this.updateSystemService.addUpdate(new SystemUpdateToVersion25(this, sqLiteDatabase)); } if (oldVersion < 27) { this.updateSystemService.addUpdate(new SystemUpdateToVersion27(sqLiteDatabase)); } if (oldVersion < 28) { this.updateSystemService.addUpdate(new SystemUpdateToVersion28(sqLiteDatabase)); } if (oldVersion < 31) { this.updateSystemService.addUpdate(new SystemUpdateToVersion31(this.context)); } if (oldVersion < 32) { this.updateSystemService.addUpdate(new SystemUpdateToVersion32(sqLiteDatabase)); } if (oldVersion < 33) { this.updateSystemService.addUpdate(new SystemUpdateToVersion33(this, sqLiteDatabase)); } if (oldVersion < 34) { this.updateSystemService.addUpdate(new SystemUpdateToVersion34(sqLiteDatabase)); } if (oldVersion < 35) { this.updateSystemService.addUpdate(new SystemUpdateToVersion35(sqLiteDatabase)); } if (oldVersion < 36) { this.updateSystemService.addUpdate(new SystemUpdateToVersion36(sqLiteDatabase)); } if (oldVersion < 37) { this.updateSystemService.addUpdate(new SystemUpdateToVersion37(this, sqLiteDatabase)); } if (oldVersion < 38) { this.updateSystemService.addUpdate(new SystemUpdateToVersion38(this, sqLiteDatabase)); } if (oldVersion < 39) { this.updateSystemService.addUpdate(new SystemUpdateToVersion39()); } if (oldVersion < 40) { this.updateSystemService.addUpdate(new SystemUpdateToVersion40(sqLiteDatabase)); } if (oldVersion < 41) { this.updateSystemService.addUpdate(new SystemUpdateToVersion41(sqLiteDatabase)); } if (oldVersion < 42) { this.updateSystemService.addUpdate(new SystemUpdateToVersion42(sqLiteDatabase)); } if (oldVersion < 43) { this.updateSystemService.addUpdate(new SystemUpdateToVersion43(sqLiteDatabase)); } if (oldVersion < 44) { this.updateSystemService.addUpdate(new SystemUpdateToVersion44(sqLiteDatabase)); } if (oldVersion < 45) { this.updateSystemService.addUpdate(new SystemUpdateToVersion45(this, sqLiteDatabase)); } if (oldVersion < 46) { this.updateSystemService.addUpdate(new SystemUpdateToVersion46()); } if (oldVersion < 47) { this.updateSystemService.addUpdate(new SystemUpdateToVersion47(sqLiteDatabase)); } if (oldVersion < 48) { this.updateSystemService.addUpdate(new SystemUpdateToVersion48(this.context)); } if (oldVersion < 49) { this.updateSystemService.addUpdate(new SystemUpdateToVersion49(sqLiteDatabase)); } if (oldVersion < 50) { this.updateSystemService.addUpdate(new SystemUpdateToVersion50(sqLiteDatabase)); } if (oldVersion < 51) { this.updateSystemService.addUpdate(new SystemUpdateToVersion51(sqLiteDatabase)); } if (oldVersion < 52) { this.updateSystemService.addUpdate(new SystemUpdateToVersion52(sqLiteDatabase)); } if (oldVersion < 53) { this.updateSystemService.addUpdate(new SystemUpdateToVersion53()); } if (oldVersion < 54) { this.updateSystemService.addUpdate(new SystemUpdateToVersion54(this.context)); } if (oldVersion < 55) { this.updateSystemService.addUpdate(new SystemUpdateToVersion55()); } if (oldVersion < 56) { this.updateSystemService.addUpdate(new SystemUpdateToVersion56(sqLiteDatabase)); } if (oldVersion < 58) { this.updateSystemService.addUpdate(new SystemUpdateToVersion58(sqLiteDatabase)); } if (oldVersion < 59) { this.updateSystemService.addUpdate(new SystemUpdateToVersion59(sqLiteDatabase)); } if (oldVersion < 60) { this.updateSystemService.addUpdate(new SystemUpdateToVersion60(sqLiteDatabase)); } if (oldVersion < 61) { this.updateSystemService.addUpdate(new SystemUpdateToVersion61(sqLiteDatabase)); } if (oldVersion < 62) { this.updateSystemService.addUpdate(new SystemUpdateToVersion62(sqLiteDatabase)); } if (oldVersion < 63) { this.updateSystemService.addUpdate(new SystemUpdateToVersion63(this.context)); } if (oldVersion < 64) { this.updateSystemService.addUpdate(new SystemUpdateToVersion64(this.context)); } if (oldVersion < SystemUpdateToVersion65.VERSION) { this.updateSystemService.addUpdate(new SystemUpdateToVersion65(this, sqLiteDatabase)); } if (oldVersion < SystemUpdateToVersion66.VERSION) { this.updateSystemService.addUpdate(new SystemUpdateToVersion66(this.context)); } if (oldVersion < SystemUpdateToVersion67.VERSION) { this.updateSystemService.addUpdate(new SystemUpdateToVersion67(sqLiteDatabase)); } if (oldVersion < SystemUpdateToVersion68.VERSION) { this.updateSystemService.addUpdate(new SystemUpdateToVersion68(sqLiteDatabase)); } if (oldVersion < SystemUpdateToVersion69.VERSION) { this.updateSystemService.addUpdate(new SystemUpdateToVersion69(this, sqLiteDatabase)); } if (oldVersion < SystemUpdateToVersion70.VERSION) { this.updateSystemService.addUpdate(new SystemUpdateToVersion70(sqLiteDatabase)); } if (oldVersion < SystemUpdateToVersion71.VERSION) { this.updateSystemService.addUpdate(new SystemUpdateToVersion71(sqLiteDatabase)); } if (oldVersion < SystemUpdateToVersion72.VERSION) { this.updateSystemService.addUpdate(new SystemUpdateToVersion72(sqLiteDatabase)); } if (oldVersion < SystemUpdateToVersion73.VERSION) { this.updateSystemService.addUpdate(new SystemUpdateToVersion73(sqLiteDatabase)); } if (oldVersion < SystemUpdateToVersion74.VERSION) { this.updateSystemService.addUpdate(new SystemUpdateToVersion74(sqLiteDatabase)); } if (oldVersion < SystemUpdateToVersion75.VERSION) { this.updateSystemService.addUpdate(new SystemUpdateToVersion75(sqLiteDatabase)); } if (oldVersion < SystemUpdateToVersion76.VERSION) { this.updateSystemService.addUpdate(new SystemUpdateToVersion76(sqLiteDatabase)); } if (oldVersion < SystemUpdateToVersion77.VERSION) { this.updateSystemService.addUpdate(new SystemUpdateToVersion77(sqLiteDatabase)); } if (oldVersion < SystemUpdateToVersion78.VERSION) { this.updateSystemService.addUpdate(new SystemUpdateToVersion78(sqLiteDatabase)); } if (oldVersion < SystemUpdateToVersion79.VERSION) { this.updateSystemService.addUpdate(new SystemUpdateToVersion79(sqLiteDatabase)); } if (oldVersion < SystemUpdateToVersion80.VERSION) { this.updateSystemService.addUpdate(new SystemUpdateToVersion80(sqLiteDatabase)); } if (oldVersion < SystemUpdateToVersion81.VERSION) { this.updateSystemService.addUpdate(new SystemUpdateToVersion81(sqLiteDatabase)); } } public void executeNull() throws SQLiteException { this.getWritableDatabase().rawExecSQL("SELECT NULL"); } @MainThread public static synchronized void tryMigrateToV4(Context context, final String databaseKey) throws DatabaseMigrationFailedException, DatabaseMigrationLockedException { File oldDatabaseFile = context.getDatabasePath(DATABASE_NAME); File newDatabaseFile = context.getDatabasePath(DATABASE_NAME_V4); final boolean[] migrateSuccess = {false}; logger.info("check if v4 database migration is necessary"); if (oldDatabaseFile.exists()) { File lockfile = new File(context.getFilesDir(), ".dbv4-lock"); if (lockfile.exists()) { long lastModified = lockfile.lastModified(); long now = System.currentTimeMillis(); if ((now - lastModified) > (5 * DateUtils.MINUTE_IN_MILLIS)) { FileUtil.deleteFileOrWarn(lockfile, "Lockfile", logger); if (newDatabaseFile.exists()) { FileUtil.deleteFileOrWarn(newDatabaseFile, "New Database File", logger); } } else { logger.info("Lockfile exists...exiting"); throw new DatabaseMigrationLockedException(); } } try { FileUtil.createNewFileOrLog(lockfile, logger); } catch (IOException e) { logger.error("IOException when creating lockfile", e); } if (!newDatabaseFile.exists()) { logger.info("Database migration to v4 required"); long usableSpace = oldDatabaseFile.getUsableSpace(); long fileSize = oldDatabaseFile.length(); if (usableSpace < (fileSize * 2)) { FileUtil.deleteFileOrWarn(lockfile, "Lockfile", logger); throw new DatabaseMigrationFailedException("Not enough space left on device"); } Thread migrateThread = new Thread(new Runnable() { @Override public void run() { try { // migrate SQLiteDatabase.loadLibs(context); SQLiteDatabaseHook hook = new SQLiteDatabaseHook() { @Override public void preKey(SQLiteDatabase sqLiteDatabase) {} @Override public void postKey(SQLiteDatabase sqLiteDatabase) { // old settings sqLiteDatabase.rawExecSQL( "PRAGMA cipher_page_size = 1024;" + "PRAGMA kdf_iter = 4000;" + "PRAGMA cipher_hmac_algorithm = HMAC_SHA1;" + "PRAGMA cipher_kdf_algorithm = PBKDF2_HMAC_SHA1;"); } }; final int databaseVersion; try (SQLiteDatabase database = SQLiteDatabase.openOrCreateDatabase(oldDatabaseFile.getAbsolutePath(), databaseKey, null, hook)) { if (database.isOpen()) { databaseVersion = database.getVersion(); logger.info("Original database version: {}", databaseVersion); database.rawExecSQL( "PRAGMA key = '" + databaseKey + "';" + "PRAGMA cipher_page_size = 1024;" + "PRAGMA kdf_iter = 4000;" + "PRAGMA cipher_hmac_algorithm = HMAC_SHA1;" + "PRAGMA cipher_kdf_algorithm = PBKDF2_HMAC_SHA1;" + "ATTACH DATABASE '" + newDatabaseFile.getAbsolutePath() + "' AS threema4 KEY '" + databaseKey + "';" + "PRAGMA threema4.kdf_iter = 1;" + "PRAGMA threema4.cipher_memory_security = OFF;" + "SELECT sqlcipher_export('threema4');" + "PRAGMA threema4.user_version = " + databaseVersion + ";" + "DETACH DATABASE threema4;"); database.close(); logger.info("Database successfully migrated"); if (checkNewDatabase(newDatabaseFile, databaseKey, databaseVersion)) { migrateSuccess[0] = true; } } } } catch (Exception e) { logger.info("Database migration FAILED"); logger.error("Exception while migrating", e); FileUtil.deleteFileOrWarn(newDatabaseFile, "New Database File", logger); } } }); migrateThread.start(); try { migrateThread.join(); } catch (InterruptedException e) { logger.error("InterruptedException while waiting for migrateThread", e); migrateSuccess[0] = false; } if (migrateSuccess[0]) { Toast.makeText(context, "Database successfully migrated", Toast.LENGTH_LONG).show(); logger.info("Migration finished"); } else { logger.info("Migration failed"); FileUtil.deleteFileOrWarn(newDatabaseFile, "New Database File", logger); FileUtil.deleteFileOrWarn(lockfile, "New Database File", logger); throw new DatabaseMigrationFailedException(); } } else { try { SQLiteDatabase.loadLibs(context); if (checkNewDatabase(newDatabaseFile, databaseKey, DATABASE_VERSION)) { logger.info("Delete old format database"); FileUtil.deleteFileOrWarn(oldDatabaseFile, "Old Database File", logger); } else { throw new Exception(); } } catch (Exception e) { logger.info("Database checking FAILED"); FileUtil.deleteFileOrWarn(newDatabaseFile, "New Database File", logger); FileUtil.deleteFileOrWarn(lockfile, "Lockfile", logger); throw new DatabaseMigrationFailedException(); } } FileUtil.deleteFileOrWarn(lockfile, "Lockfile", logger); } else { logger.info("No old database file found. No migration necessary"); logger.info("New database file exists = {}", newDatabaseFile.exists()); } } private static boolean checkNewDatabase(File newDatabaseFile, String databaseKey, int databaseVersion) { // test new database try (SQLiteDatabase newDatabase = SQLiteDatabase.openDatabase(newDatabaseFile.getAbsolutePath(), databaseKey, null, 0, new SQLiteDatabaseHook() { @Override public void preKey(SQLiteDatabase sqLiteDatabase) { sqLiteDatabase.rawExecSQL("PRAGMA cipher_default_kdf_iter = 1;"); } @Override public void postKey(SQLiteDatabase sqLiteDatabase) { sqLiteDatabase.rawExecSQL( "PRAGMA kdf_iter = 1;" + "PRAGMA cipher_memory_security = OFF;"); } })) { if (newDatabase.isOpen()) { if (newDatabase.getVersion() == databaseVersion) { newDatabase.rawExecSQL("SELECT NULL;"); logger.info("New database successfully checked. Version set to {}", databaseVersion); return true; } else { logger.info("Database version mismatch. old = {} new = {}", databaseVersion, newDatabase.getVersion()); } } else { logger.info("Could not open new database"); } } return false; } }