2012-09-18 18:35:14 +02:00
|
|
|
/*
|
2017-12-15 15:16:48 +01:00
|
|
|
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
2012-09-18 18:35:14 +02:00
|
|
|
*
|
2013-09-06 16:17:01 +02:00
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
* (at your option) any later version.
|
2012-09-18 18:35:14 +02:00
|
|
|
*
|
2013-09-06 16:17:01 +02:00
|
|
|
* 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 General Public License for more details.
|
2012-09-18 18:35:14 +02:00
|
|
|
*
|
2013-09-06 16:17:01 +02:00
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
2012-09-18 18:35:14 +02:00
|
|
|
*/
|
|
|
|
|
|
2018-07-02 15:07:11 +02:00
|
|
|
package org.sufficientlysecure.keychain;
|
2012-09-18 18:35:14 +02:00
|
|
|
|
2017-02-20 21:57:38 +01:00
|
|
|
|
|
|
|
|
import java.io.File;
|
|
|
|
|
import java.io.FileInputStream;
|
|
|
|
|
import java.io.FileOutputStream;
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
|
2018-06-15 19:46:55 +02:00
|
|
|
import android.arch.persistence.db.SupportSQLiteDatabase;
|
|
|
|
|
import android.arch.persistence.db.SupportSQLiteOpenHelper;
|
|
|
|
|
import android.arch.persistence.db.SupportSQLiteOpenHelper.Callback;
|
|
|
|
|
import android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration;
|
|
|
|
|
import android.arch.persistence.db.framework.FrameworkSQLiteOpenHelperFactory;
|
2014-03-13 23:03:08 +02:00
|
|
|
import android.content.Context;
|
2018-06-15 14:34:38 +02:00
|
|
|
import android.database.Cursor;
|
2018-06-18 23:03:09 +02:00
|
|
|
import android.database.SQLException;
|
2017-08-04 13:08:39 +02:00
|
|
|
import android.database.sqlite.SQLiteException;
|
2014-03-26 00:11:49 +01:00
|
|
|
|
2018-07-02 15:07:11 +02:00
|
|
|
import org.sufficientlysecure.keychain.daos.LocalSecretKeyStorage;
|
2014-04-15 22:52:21 +02:00
|
|
|
import org.sufficientlysecure.keychain.provider.KeychainContract.CertsColumns;
|
2017-09-22 04:20:09 +02:00
|
|
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns;
|
2015-01-13 23:09:48 +01:00
|
|
|
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPacketsColumns;
|
2017-09-23 01:00:14 +02:00
|
|
|
import org.sufficientlysecure.keychain.util.Preferences;
|
2018-01-20 02:12:43 +01:00
|
|
|
import timber.log.Timber;
|
2017-09-23 01:00:14 +02:00
|
|
|
|
2012-09-18 18:35:14 +02:00
|
|
|
|
2014-04-29 10:19:10 +02:00
|
|
|
/**
|
|
|
|
|
* SQLite Datatypes (from http://www.sqlite.org/datatype3.html)
|
|
|
|
|
* - NULL. The value is a NULL value.
|
|
|
|
|
* - INTEGER. The value is a signed integer, stored in 1, 2, 3, 4, 6, or 8 bytes depending on the magnitude of the value.
|
|
|
|
|
* - REAL. The value is a floating point value, stored as an 8-byte IEEE floating point number.
|
|
|
|
|
* - TEXT. The value is a text string, stored using the database encoding (UTF-8, UTF-16BE or UTF-16LE).
|
|
|
|
|
* - BLOB. The value is a blob of data, stored exactly as it was input.
|
|
|
|
|
*/
|
2018-06-15 19:46:55 +02:00
|
|
|
public class KeychainDatabase {
|
2014-04-03 01:34:35 +02:00
|
|
|
private static final String DATABASE_NAME = "openkeychain.db";
|
2018-07-24 15:24:54 +02:00
|
|
|
private static final int DATABASE_VERSION = 30;
|
2018-06-15 19:46:55 +02:00
|
|
|
private final SupportSQLiteOpenHelper supportSQLiteOpenHelper;
|
2018-06-25 15:11:08 +02:00
|
|
|
|
|
|
|
|
private static KeychainDatabase sInstance;
|
|
|
|
|
|
|
|
|
|
public static KeychainDatabase getInstance(Context context) {
|
2018-07-04 22:09:52 +02:00
|
|
|
if (sInstance == null || Constants.IS_RUNNING_UNITTEST) {
|
2018-06-25 15:11:08 +02:00
|
|
|
sInstance = new KeychainDatabase(context.getApplicationContext());
|
2018-07-04 22:09:52 +02:00
|
|
|
}
|
2018-06-25 15:11:08 +02:00
|
|
|
return sInstance;
|
|
|
|
|
}
|
2012-09-18 18:35:14 +02:00
|
|
|
|
2018-07-05 20:13:58 +02:00
|
|
|
public static KeychainDatabase getTemporaryInstance(Context context) {
|
|
|
|
|
return new KeychainDatabase(context.getApplicationContext());
|
|
|
|
|
}
|
|
|
|
|
|
2012-09-18 18:35:14 +02:00
|
|
|
public interface Tables {
|
2014-04-03 01:34:35 +02:00
|
|
|
String KEY_RINGS_PUBLIC = "keyrings_public";
|
2012-09-18 18:35:14 +02:00
|
|
|
String KEYS = "keys";
|
2017-09-22 04:18:14 +02:00
|
|
|
String KEY_SIGNATURES = "key_signatures";
|
2015-03-11 18:28:36 +01:00
|
|
|
String USER_PACKETS = "user_packets";
|
2014-03-11 00:09:11 +01:00
|
|
|
String CERTS = "certs";
|
2015-01-29 17:46:27 +01:00
|
|
|
String API_ALLOWED_KEYS = "api_allowed_keys";
|
2017-04-28 23:38:37 +02:00
|
|
|
String OVERRIDDEN_WARNINGS = "overridden_warnings";
|
2012-09-18 18:35:14 +02:00
|
|
|
}
|
|
|
|
|
|
2018-06-25 15:11:08 +02:00
|
|
|
private KeychainDatabase(Context context) {
|
2018-06-15 19:46:55 +02:00
|
|
|
supportSQLiteOpenHelper =
|
|
|
|
|
new FrameworkSQLiteOpenHelperFactory()
|
|
|
|
|
.create(Configuration.builder(context).name(DATABASE_NAME).callback(
|
|
|
|
|
new Callback(DATABASE_VERSION) {
|
|
|
|
|
@Override
|
|
|
|
|
public void onCreate(SupportSQLiteDatabase db) {
|
2018-06-25 15:11:08 +02:00
|
|
|
KeychainDatabase.this.onCreate(db, context);
|
2018-06-15 19:46:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
|
2018-06-25 15:11:08 +02:00
|
|
|
KeychainDatabase.this.onUpgrade(db, context, oldVersion, newVersion);
|
2018-06-15 19:46:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
|
2018-06-25 15:11:08 +02:00
|
|
|
KeychainDatabase.this.onDowngrade();
|
2018-06-15 19:46:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onOpen(SupportSQLiteDatabase db) {
|
|
|
|
|
super.onOpen(db);
|
|
|
|
|
if (!db.isReadOnly()) {
|
|
|
|
|
// Enable foreign key constraints
|
|
|
|
|
db.execSQL("PRAGMA foreign_keys=ON;");
|
2018-07-04 21:57:52 +02:00
|
|
|
if (Constants.DEBUG) {
|
|
|
|
|
recreateUnifiedKeyView(db);
|
|
|
|
|
}
|
2018-06-15 19:46:55 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}).build());
|
2012-09-18 18:35:14 +02:00
|
|
|
}
|
|
|
|
|
|
2018-06-15 19:46:55 +02:00
|
|
|
public SupportSQLiteDatabase getReadableDatabase() {
|
|
|
|
|
return supportSQLiteOpenHelper.getReadableDatabase();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public SupportSQLiteDatabase getWritableDatabase() {
|
|
|
|
|
return supportSQLiteOpenHelper.getWritableDatabase();
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-25 15:11:08 +02:00
|
|
|
private void onCreate(SupportSQLiteDatabase db, Context context) {
|
2018-01-20 02:12:43 +01:00
|
|
|
Timber.w("Creating database...");
|
2012-09-18 18:35:14 +02:00
|
|
|
|
2018-06-18 11:40:39 +02:00
|
|
|
db.execSQL(KeyRingsPublicModel.CREATE_TABLE);
|
2018-06-25 15:11:08 +02:00
|
|
|
db.execSQL(KeysModel.CREATE_TABLE);
|
2018-06-20 00:45:34 +02:00
|
|
|
db.execSQL(UserPacketsModel.CREATE_TABLE);
|
|
|
|
|
db.execSQL(CertsModel.CREATE_TABLE);
|
2018-06-18 11:40:39 +02:00
|
|
|
db.execSQL(KeyMetadataModel.CREATE_TABLE);
|
2018-06-22 19:58:17 +02:00
|
|
|
db.execSQL(KeySignaturesModel.CREATE_TABLE);
|
2018-06-25 15:11:08 +02:00
|
|
|
db.execSQL(ApiAppsModel.CREATE_TABLE);
|
|
|
|
|
db.execSQL(OverriddenWarningsModel.CREATE_TABLE);
|
2018-06-18 23:03:09 +02:00
|
|
|
db.execSQL(AutocryptPeersModel.CREATE_TABLE);
|
2018-06-26 10:24:19 +02:00
|
|
|
db.execSQL(ApiAllowedKeysModel.CREATE_TABLE);
|
2018-06-25 14:12:22 +02:00
|
|
|
db.execSQL(KeysModel.UNIFIEDKEYVIEW);
|
2018-07-07 03:43:37 +02:00
|
|
|
db.execSQL(KeysModel.VALIDKEYSVIEW);
|
|
|
|
|
db.execSQL(UserPacketsModel.UIDSTATUS);
|
2015-11-15 02:45:21 +01:00
|
|
|
|
2018-01-15 17:01:21 +01:00
|
|
|
db.execSQL("CREATE INDEX keys_by_rank ON keys (" + KeysColumns.RANK + ", " + KeysColumns.MASTER_KEY_ID + ");");
|
2015-11-15 02:45:21 +01:00
|
|
|
db.execSQL("CREATE INDEX uids_by_rank ON user_packets (" + UserPacketsColumns.RANK + ", "
|
|
|
|
|
+ UserPacketsColumns.USER_ID + ", " + UserPacketsColumns.MASTER_KEY_ID + ");");
|
|
|
|
|
db.execSQL("CREATE INDEX verified_certs ON certs ("
|
|
|
|
|
+ CertsColumns.VERIFIED + ", " + CertsColumns.MASTER_KEY_ID + ");");
|
2018-01-15 02:55:57 +01:00
|
|
|
db.execSQL("CREATE INDEX uids_by_email ON user_packets ("
|
|
|
|
|
+ UserPacketsColumns.EMAIL + ");");
|
2015-11-15 02:45:21 +01:00
|
|
|
|
2018-06-15 19:46:55 +02:00
|
|
|
Preferences.getPreferences(context).setKeySignaturesTableInitialized();
|
2012-09-18 18:35:14 +02:00
|
|
|
}
|
|
|
|
|
|
2018-06-25 15:11:08 +02:00
|
|
|
private void onUpgrade(SupportSQLiteDatabase db, Context context, int oldVersion, int newVersion) {
|
2018-01-20 02:12:43 +01:00
|
|
|
Timber.d("Upgrading db from " + oldVersion + " to " + newVersion);
|
2014-09-09 09:26:03 +02:00
|
|
|
|
2014-08-24 05:12:09 +02:00
|
|
|
switch (oldVersion) {
|
|
|
|
|
case 1:
|
2014-09-09 09:26:03 +02:00
|
|
|
// add has_secret for all who are upgrading from a beta version
|
2014-08-24 05:12:09 +02:00
|
|
|
try {
|
2014-10-29 23:41:10 +01:00
|
|
|
db.execSQL("ALTER TABLE keys ADD COLUMN has_secret INTEGER");
|
2014-09-30 17:50:16 +02:00
|
|
|
} catch (Exception e) {
|
2014-08-24 05:12:09 +02:00
|
|
|
// never mind, the column probably already existed
|
|
|
|
|
}
|
2014-09-09 09:26:03 +02:00
|
|
|
// fall through
|
2014-08-24 05:12:09 +02:00
|
|
|
case 2:
|
2014-09-09 09:26:03 +02:00
|
|
|
// ECC support
|
2014-08-24 05:12:09 +02:00
|
|
|
try {
|
2014-09-20 20:36:37 +02:00
|
|
|
db.execSQL("ALTER TABLE keys ADD COLUMN key_curve_oid TEXT");
|
2014-09-30 17:50:16 +02:00
|
|
|
} catch (Exception e) {
|
2014-08-24 05:12:09 +02:00
|
|
|
// never mind, the column probably already existed
|
|
|
|
|
}
|
2014-09-09 09:26:03 +02:00
|
|
|
// fall through
|
|
|
|
|
case 3:
|
|
|
|
|
// better s2k detection, we need consolidate
|
|
|
|
|
// fall through
|
2014-09-20 20:36:37 +02:00
|
|
|
case 4:
|
|
|
|
|
try {
|
2014-10-29 23:41:10 +01:00
|
|
|
db.execSQL("ALTER TABLE keys ADD COLUMN can_authenticate INTEGER");
|
2014-09-30 17:50:16 +02:00
|
|
|
} catch (Exception e) {
|
2014-09-20 20:36:37 +02:00
|
|
|
// never mind, the column probably already existed
|
|
|
|
|
}
|
|
|
|
|
// fall through
|
2014-09-30 17:50:16 +02:00
|
|
|
case 5:
|
|
|
|
|
// do consolidate for 3.0 beta3
|
|
|
|
|
// fall through
|
2015-01-13 23:09:48 +01:00
|
|
|
case 6:
|
|
|
|
|
db.execSQL("ALTER TABLE user_ids ADD COLUMN type INTEGER");
|
|
|
|
|
db.execSQL("ALTER TABLE user_ids ADD COLUMN attribute_data BLOB");
|
2015-01-29 17:46:27 +01:00
|
|
|
case 7:
|
|
|
|
|
// new table for allowed key ids in API
|
|
|
|
|
try {
|
2018-06-25 15:11:08 +02:00
|
|
|
db.execSQL(ApiAppsModel.CREATE_TABLE);
|
2015-01-29 17:46:27 +01:00
|
|
|
} catch (Exception e) {
|
|
|
|
|
// never mind, the column probably already existed
|
|
|
|
|
}
|
2016-03-15 23:30:50 +01:00
|
|
|
case 8:
|
2015-03-11 18:28:36 +01:00
|
|
|
// tbale name for user_ids changed to user_packets
|
|
|
|
|
db.execSQL("DROP TABLE IF EXISTS certs");
|
|
|
|
|
db.execSQL("DROP TABLE IF EXISTS user_ids");
|
2017-08-04 13:08:39 +02:00
|
|
|
db.execSQL("CREATE TABLE IF NOT EXISTS user_packets("
|
2018-06-15 14:34:38 +02:00
|
|
|
+ "master_key_id INTEGER, "
|
|
|
|
|
+ "type INT, "
|
|
|
|
|
+ "user_id TEXT, "
|
|
|
|
|
+ "attribute_data BLOB, "
|
2017-08-04 13:08:39 +02:00
|
|
|
|
2018-06-15 14:34:38 +02:00
|
|
|
+ "is_primary INTEGER, "
|
|
|
|
|
+ "is_revoked INTEGER, "
|
|
|
|
|
+ "rank INTEGER, "
|
2017-08-04 13:08:39 +02:00
|
|
|
|
2018-06-15 14:34:38 +02:00
|
|
|
+ "PRIMARY KEY(master_key_id, rank), "
|
|
|
|
|
+ "FOREIGN KEY(master_key_id) REFERENCES "
|
2017-08-04 13:08:39 +02:00
|
|
|
+ "keyrings_public(master_key_id) ON DELETE CASCADE"
|
2018-06-15 14:34:38 +02:00
|
|
|
+ ")");
|
2017-08-04 13:08:39 +02:00
|
|
|
db.execSQL("CREATE TABLE IF NOT EXISTS certs("
|
2018-06-15 14:34:38 +02:00
|
|
|
+ "master_key_id INTEGER,"
|
|
|
|
|
+ "rank INTEGER, " // rank of certified uid
|
2017-08-04 13:08:39 +02:00
|
|
|
|
2018-06-15 14:34:38 +02:00
|
|
|
+ "key_id_certifier INTEGER, " // certifying key
|
|
|
|
|
+ "type INTEGER, "
|
|
|
|
|
+ "verified INTEGER, "
|
|
|
|
|
+ "creation INTEGER, "
|
2017-08-04 13:08:39 +02:00
|
|
|
|
2018-06-15 14:34:38 +02:00
|
|
|
+ "data BLOB, "
|
2017-08-04 13:08:39 +02:00
|
|
|
|
2018-06-15 14:34:38 +02:00
|
|
|
+ "PRIMARY KEY(master_key_id, rank, "
|
2017-08-04 13:08:39 +02:00
|
|
|
+ "key_id_certifier), "
|
2018-06-15 14:34:38 +02:00
|
|
|
+ "FOREIGN KEY(master_key_id) REFERENCES "
|
2017-08-04 13:08:39 +02:00
|
|
|
+ "keyrings_public(master_key_id) ON DELETE CASCADE,"
|
2018-06-15 14:34:38 +02:00
|
|
|
+ "FOREIGN KEY(master_key_id, rank) REFERENCES "
|
2017-08-04 13:08:39 +02:00
|
|
|
+ "user_packets(master_key_id, rank) ON DELETE CASCADE"
|
2018-06-15 14:34:38 +02:00
|
|
|
+ ")");
|
2016-03-15 23:30:50 +01:00
|
|
|
case 9:
|
2015-05-28 22:43:10 +02:00
|
|
|
// do nothing here, just consolidate
|
2016-03-15 23:30:50 +01:00
|
|
|
case 10:
|
2015-07-08 03:33:46 +02:00
|
|
|
// fix problems in database, see #1402 for details
|
|
|
|
|
// https://github.com/open-keychain/open-keychain/issues/1402
|
2017-04-23 13:38:40 +02:00
|
|
|
// no longer needed, api_accounts is deprecated
|
|
|
|
|
// db.execSQL("DELETE FROM api_accounts WHERE key_id BETWEEN 0 AND 3");
|
2016-03-15 23:30:50 +01:00
|
|
|
case 11:
|
2017-08-04 13:08:39 +02:00
|
|
|
db.execSQL("CREATE TABLE IF NOT EXISTS updated_keys ("
|
|
|
|
|
+ "master_key_id INTEGER PRIMARY KEY, "
|
|
|
|
|
+ "last_updated INTEGER, "
|
|
|
|
|
+ "FOREIGN KEY(master_key_id) REFERENCES "
|
|
|
|
|
+ "keyrings_public(master_key_id) ON DELETE CASCADE"
|
|
|
|
|
+ ")");
|
2016-03-15 23:30:50 +01:00
|
|
|
case 12:
|
2015-09-25 00:01:24 +02:00
|
|
|
// do nothing here, just consolidate
|
2016-03-15 23:30:50 +01:00
|
|
|
case 13:
|
2015-11-15 02:45:21 +01:00
|
|
|
db.execSQL("CREATE INDEX keys_by_rank ON keys (" + KeysColumns.RANK + ");");
|
|
|
|
|
db.execSQL("CREATE INDEX uids_by_rank ON user_packets (" + UserPacketsColumns.RANK + ", "
|
|
|
|
|
+ UserPacketsColumns.USER_ID + ", " + UserPacketsColumns.MASTER_KEY_ID + ");");
|
|
|
|
|
db.execSQL("CREATE INDEX verified_certs ON certs ("
|
|
|
|
|
+ CertsColumns.VERIFIED + ", " + CertsColumns.MASTER_KEY_ID + ");");
|
2016-03-15 23:30:50 +01:00
|
|
|
case 14:
|
2016-03-07 16:40:26 +02:00
|
|
|
db.execSQL("ALTER TABLE user_packets ADD COLUMN name TEXT");
|
|
|
|
|
db.execSQL("ALTER TABLE user_packets ADD COLUMN email TEXT");
|
|
|
|
|
db.execSQL("ALTER TABLE user_packets ADD COLUMN comment TEXT");
|
2016-03-16 00:33:09 +01:00
|
|
|
case 15:
|
|
|
|
|
db.execSQL("CREATE INDEX uids_by_name ON user_packets (name COLLATE NOCASE)");
|
|
|
|
|
db.execSQL("CREATE INDEX uids_by_email ON user_packets (email COLLATE NOCASE)");
|
2017-01-26 23:04:09 +01:00
|
|
|
case 16:
|
|
|
|
|
// splitUserId changed: Execute consolidate for new parsing of name, email
|
|
|
|
|
case 17:
|
|
|
|
|
// splitUserId changed: Execute consolidate for new parsing of name, email
|
2017-02-03 16:11:29 +05:30
|
|
|
case 18:
|
|
|
|
|
db.execSQL("ALTER TABLE keys ADD COLUMN is_secure INTEGER");
|
2017-02-07 13:15:35 +01:00
|
|
|
case 19:
|
|
|
|
|
// emergency fix for crashing consolidate
|
|
|
|
|
db.execSQL("UPDATE keys SET is_secure = 1;");
|
2017-04-28 23:38:37 +02:00
|
|
|
case 20:
|
2018-06-15 14:34:38 +02:00
|
|
|
db.execSQL(
|
|
|
|
|
"CREATE TABLE IF NOT EXISTS overridden_warnings ("
|
|
|
|
|
+ "_id INTEGER PRIMARY KEY AUTOINCREMENT, "
|
|
|
|
|
+ "identifier TEXT NOT NULL UNIQUE "
|
|
|
|
|
+ ")");
|
2017-07-24 11:42:38 +02:00
|
|
|
|
2017-05-20 21:37:04 +02:00
|
|
|
case 21:
|
2017-08-04 13:08:39 +02:00
|
|
|
try {
|
|
|
|
|
db.execSQL("ALTER TABLE updated_keys ADD COLUMN seen_on_keyservers INTEGER;");
|
|
|
|
|
} catch (SQLiteException e) {
|
|
|
|
|
// don't bother, the column probably already existed
|
|
|
|
|
}
|
2017-05-20 21:37:04 +02:00
|
|
|
|
2016-11-28 08:11:34 +01:00
|
|
|
case 22:
|
2017-06-13 18:33:21 +02:00
|
|
|
db.execSQL("CREATE TABLE IF NOT EXISTS api_autocrypt_peers ("
|
|
|
|
|
+ "package_name TEXT NOT NULL, "
|
|
|
|
|
+ "identifier TEXT NOT NULL, "
|
|
|
|
|
+ "last_updated INTEGER NOT NULL, "
|
2017-07-25 23:19:02 +02:00
|
|
|
+ "last_seen_key INTEGER NOT NULL, "
|
|
|
|
|
+ "state INTEGER NOT NULL, "
|
2018-06-25 15:11:08 +02:00
|
|
|
+ "master_key_id INTEGER, "
|
2017-06-13 18:33:21 +02:00
|
|
|
+ "PRIMARY KEY(package_name, identifier), "
|
|
|
|
|
+ "FOREIGN KEY(package_name) REFERENCES api_apps(package_name) ON DELETE CASCADE"
|
2018-06-15 14:34:38 +02:00
|
|
|
+ ")");
|
2016-11-28 08:11:34 +01:00
|
|
|
|
2017-09-22 04:18:14 +02:00
|
|
|
case 23:
|
|
|
|
|
db.execSQL("CREATE TABLE IF NOT EXISTS key_signatures ("
|
|
|
|
|
+ "master_key_id INTEGER NOT NULL, "
|
|
|
|
|
+ "signer_key_id INTEGER NOT NULL, "
|
|
|
|
|
+ "PRIMARY KEY(master_key_id, signer_key_id), "
|
|
|
|
|
+ "FOREIGN KEY(master_key_id) REFERENCES keyrings_public(master_key_id) ON DELETE CASCADE"
|
|
|
|
|
+ ")");
|
2018-01-08 12:38:57 +01:00
|
|
|
|
2018-06-15 14:34:38 +02:00
|
|
|
case 24: {
|
2018-01-30 11:20:23 +01:00
|
|
|
try {
|
|
|
|
|
db.beginTransaction();
|
|
|
|
|
db.execSQL("ALTER TABLE api_autocrypt_peers RENAME TO tmp");
|
|
|
|
|
db.execSQL("CREATE TABLE api_autocrypt_peers ("
|
|
|
|
|
+ "package_name TEXT NOT NULL, "
|
|
|
|
|
+ "identifier TEXT NOT NULL, "
|
|
|
|
|
+ "last_seen INTEGER, "
|
|
|
|
|
+ "last_seen_key INTEGER, "
|
|
|
|
|
+ "is_mutual INTEGER, "
|
|
|
|
|
+ "master_key_id INTEGER, "
|
|
|
|
|
+ "gossip_master_key_id INTEGER, "
|
|
|
|
|
+ "gossip_last_seen_key INTEGER, "
|
|
|
|
|
+ "gossip_origin INTEGER, "
|
|
|
|
|
+ "PRIMARY KEY(package_name, identifier), "
|
|
|
|
|
+ "FOREIGN KEY(package_name) REFERENCES api_apps (package_name) ON DELETE CASCADE"
|
|
|
|
|
+ ")");
|
|
|
|
|
// Note: Keys from Autocrypt 0.X with state == "reset" (0) are dropped
|
|
|
|
|
db.execSQL("INSERT INTO api_autocrypt_peers " +
|
|
|
|
|
"(package_name, identifier, last_seen, gossip_last_seen_key, gossip_master_key_id, gossip_origin) " +
|
|
|
|
|
"SELECT package_name, identifier, last_updated, last_seen_key, master_key_id, 0 " +
|
|
|
|
|
"FROM tmp WHERE state = 1"); // Autocrypt 0.X, "gossip" -> now origin=autocrypt
|
|
|
|
|
db.execSQL("INSERT INTO api_autocrypt_peers " +
|
|
|
|
|
"(package_name, identifier, last_seen, gossip_last_seen_key, gossip_master_key_id, gossip_origin) " +
|
|
|
|
|
"SELECT package_name, identifier, last_updated, last_seen_key, master_key_id, 20 " +
|
|
|
|
|
"FROM tmp WHERE state = 2"); // "selected" keys -> now origin=dedup
|
|
|
|
|
db.execSQL("INSERT INTO api_autocrypt_peers " +
|
|
|
|
|
"(package_name, identifier, last_seen, last_seen_key, master_key_id, is_mutual) " +
|
|
|
|
|
"SELECT package_name, identifier, last_updated, last_seen_key, master_key_id, 0 " +
|
2018-01-20 22:08:51 +01:00
|
|
|
"FROM tmp WHERE state = 3"); // Autocrypt 0.X, state = "available"
|
2018-01-30 11:20:23 +01:00
|
|
|
db.execSQL("INSERT INTO api_autocrypt_peers " +
|
|
|
|
|
"(package_name, identifier, last_seen, last_seen_key, master_key_id, is_mutual) " +
|
|
|
|
|
"SELECT package_name, identifier, last_updated, last_seen_key, master_key_id, 1 " +
|
2018-01-20 22:08:51 +01:00
|
|
|
"FROM tmp WHERE state = 4"); // from Autocrypt 0.X, state = "mutual"
|
2018-01-30 11:20:23 +01:00
|
|
|
db.execSQL("DROP TABLE tmp");
|
|
|
|
|
db.setTransactionSuccessful();
|
|
|
|
|
} finally {
|
|
|
|
|
db.endTransaction();
|
|
|
|
|
}
|
2018-01-17 15:40:46 +01:00
|
|
|
|
2018-01-30 11:20:23 +01:00
|
|
|
db.execSQL("CREATE INDEX IF NOT EXISTS uids_by_email ON user_packets (email);");
|
2018-01-15 17:01:21 +01:00
|
|
|
db.execSQL("DROP INDEX keys_by_rank");
|
|
|
|
|
db.execSQL("CREATE INDEX keys_by_rank ON keys(rank, master_key_id);");
|
2018-06-15 14:34:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case 25: {
|
|
|
|
|
try {
|
2018-06-25 15:11:08 +02:00
|
|
|
migrateSecretKeysFromDbToLocalStorage(db, context);
|
2018-06-15 14:34:38 +02:00
|
|
|
} catch (IOException e) {
|
|
|
|
|
throw new IllegalStateException("Error migrating secret keys! This is bad!!");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-25 14:12:22 +02:00
|
|
|
case 26:
|
2018-06-18 11:40:39 +02:00
|
|
|
migrateUpdatedKeysToKeyMetadataTable(db);
|
2018-06-18 23:03:09 +02:00
|
|
|
|
2018-06-25 14:12:22 +02:00
|
|
|
case 27:
|
2018-06-18 23:03:09 +02:00
|
|
|
renameApiAutocryptPeersTable(db);
|
2018-06-25 14:12:22 +02:00
|
|
|
|
|
|
|
|
case 28:
|
2018-06-25 15:11:08 +02:00
|
|
|
// drop old table from version 20
|
|
|
|
|
db.execSQL("DROP TABLE IF EXISTS api_accounts");
|
2018-07-07 03:43:37 +02:00
|
|
|
|
|
|
|
|
case 29:
|
|
|
|
|
recreateUnifiedKeyView(db);
|
2014-04-22 11:38:39 +02:00
|
|
|
}
|
2014-04-03 01:34:35 +02:00
|
|
|
}
|
2014-03-11 00:09:11 +01:00
|
|
|
|
2018-06-25 14:12:22 +02:00
|
|
|
private void recreateUnifiedKeyView(SupportSQLiteDatabase db) {
|
2018-07-12 17:38:10 +02:00
|
|
|
try {
|
|
|
|
|
db.beginTransaction();
|
2018-07-07 03:43:37 +02:00
|
|
|
|
2018-07-12 17:38:10 +02:00
|
|
|
// noinspection deprecation
|
|
|
|
|
db.execSQL("DROP VIEW IF EXISTS " + KeysModel.UNIFIEDKEYVIEW_VIEW_NAME);
|
|
|
|
|
db.execSQL(KeysModel.UNIFIEDKEYVIEW);
|
2018-07-07 03:43:37 +02:00
|
|
|
// noinspection deprecation
|
|
|
|
|
db.execSQL("DROP VIEW IF EXISTS " + KeysModel.VALIDMASTERKEYS_VIEW_NAME);
|
|
|
|
|
db.execSQL(KeysModel.VALIDKEYSVIEW);
|
|
|
|
|
// noinspection deprecation
|
|
|
|
|
db.execSQL("DROP VIEW IF EXISTS " + UserPacketsModel.UIDSTATUS_VIEW_NAME);
|
|
|
|
|
db.execSQL(UserPacketsModel.UIDSTATUS);
|
|
|
|
|
|
2018-07-12 17:38:10 +02:00
|
|
|
db.setTransactionSuccessful();
|
|
|
|
|
} finally {
|
|
|
|
|
db.endTransaction();
|
|
|
|
|
}
|
2018-06-25 14:12:22 +02:00
|
|
|
}
|
|
|
|
|
|
2018-06-25 15:11:08 +02:00
|
|
|
private void migrateSecretKeysFromDbToLocalStorage(SupportSQLiteDatabase db, Context context) throws IOException {
|
2018-06-15 19:46:55 +02:00
|
|
|
LocalSecretKeyStorage localSecretKeyStorage = LocalSecretKeyStorage.getInstance(context);
|
|
|
|
|
Cursor cursor = db.query("SELECT master_key_id, key_ring_data FROM keyrings_secret");
|
2018-06-15 14:34:38 +02:00
|
|
|
while (cursor.moveToNext()) {
|
|
|
|
|
long masterKeyId = cursor.getLong(0);
|
|
|
|
|
byte[] secretKeyBlob = cursor.getBlob(1);
|
|
|
|
|
localSecretKeyStorage.writeSecretKey(masterKeyId, secretKeyBlob);
|
|
|
|
|
}
|
|
|
|
|
cursor.close();
|
|
|
|
|
|
|
|
|
|
// we'll keep this around for now, but make sure to delete when migration looks ok!!
|
|
|
|
|
// db.execSQL("DROP TABLE keyrings_secret");
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-18 11:40:39 +02:00
|
|
|
private void migrateUpdatedKeysToKeyMetadataTable(SupportSQLiteDatabase db) {
|
2018-06-21 14:24:52 +02:00
|
|
|
try {
|
|
|
|
|
db.execSQL("ALTER TABLE updated_keys RENAME TO key_metadata;");
|
|
|
|
|
} catch (SQLException e) {
|
|
|
|
|
if (Constants.DEBUG) {
|
|
|
|
|
Timber.e(e, "Ignoring migration exception, this probably happened before");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
throw e;
|
|
|
|
|
}
|
2018-06-18 11:40:39 +02:00
|
|
|
}
|
|
|
|
|
|
2018-06-18 23:03:09 +02:00
|
|
|
private void renameApiAutocryptPeersTable(SupportSQLiteDatabase db) {
|
2018-06-21 14:24:52 +02:00
|
|
|
try {
|
|
|
|
|
db.execSQL("ALTER TABLE api_autocrypt_peers RENAME TO autocrypt_peers;");
|
|
|
|
|
} catch (SQLException e) {
|
|
|
|
|
if (Constants.DEBUG) {
|
|
|
|
|
Timber.e(e, "Ignoring migration exception, this probably happened before");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
throw e;
|
|
|
|
|
}
|
2018-06-18 23:03:09 +02:00
|
|
|
}
|
|
|
|
|
|
2018-06-25 15:11:08 +02:00
|
|
|
private void onDowngrade() {
|
2015-11-15 03:10:30 +01:00
|
|
|
// Downgrade is ok for the debug version, makes it easier to work with branches
|
|
|
|
|
if (Constants.DEBUG) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2015-09-25 00:01:24 +02:00
|
|
|
// NOTE: downgrading the database is explicitly not allowed to prevent
|
|
|
|
|
// someone from exploiting old bugs to export the database
|
|
|
|
|
throw new RuntimeException("Downgrading the database is not allowed!");
|
|
|
|
|
}
|
|
|
|
|
|
2014-04-11 20:25:50 +02:00
|
|
|
private static void copy(File in, File out) throws IOException {
|
2014-08-04 11:19:46 +02:00
|
|
|
FileInputStream is = new FileInputStream(in);
|
|
|
|
|
FileOutputStream os = new FileOutputStream(out);
|
2015-06-17 21:30:25 +02:00
|
|
|
try {
|
|
|
|
|
byte[] buf = new byte[512];
|
|
|
|
|
while (is.available() > 0) {
|
|
|
|
|
int count = is.read(buf, 0, 512);
|
|
|
|
|
os.write(buf, 0, count);
|
|
|
|
|
}
|
|
|
|
|
} finally {
|
|
|
|
|
is.close();
|
|
|
|
|
os.close();
|
2014-04-11 20:25:50 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-08-04 11:19:46 +02:00
|
|
|
public static void debugBackup(Context context, boolean restore) throws IOException {
|
2014-04-29 14:02:22 +02:00
|
|
|
if (!Constants.DEBUG) {
|
2014-04-11 20:25:50 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2014-08-04 11:19:46 +02:00
|
|
|
File in;
|
|
|
|
|
File out;
|
|
|
|
|
if (restore) {
|
|
|
|
|
in = context.getDatabasePath("debug_backup.db");
|
2014-08-04 14:42:03 +02:00
|
|
|
out = context.getDatabasePath(DATABASE_NAME);
|
2014-08-04 11:19:46 +02:00
|
|
|
} else {
|
2014-08-04 14:42:03 +02:00
|
|
|
in = context.getDatabasePath(DATABASE_NAME);
|
2014-08-04 11:19:46 +02:00
|
|
|
out = context.getDatabasePath("debug_backup.db");
|
2015-07-08 02:55:35 +02:00
|
|
|
// noinspection ResultOfMethodCallIgnored - this is a pure debug feature, anyways
|
2014-08-04 11:19:46 +02:00
|
|
|
out.createNewFile();
|
2014-04-11 20:25:50 +02:00
|
|
|
}
|
2014-04-29 14:02:22 +02:00
|
|
|
if (!in.canRead()) {
|
2014-04-11 20:25:50 +02:00
|
|
|
throw new IOException("Cannot read " + in.getName());
|
|
|
|
|
}
|
2014-08-04 11:19:46 +02:00
|
|
|
if (!out.canWrite()) {
|
2014-04-11 20:25:50 +02:00
|
|
|
throw new IOException("Cannot write " + out.getName());
|
|
|
|
|
}
|
|
|
|
|
copy(in, out);
|
|
|
|
|
}
|
2014-08-04 11:19:46 +02:00
|
|
|
|
2014-08-20 19:31:51 +02:00
|
|
|
// DANGEROUS, use in test code ONLY!
|
2014-08-14 17:10:08 +02:00
|
|
|
public void clearDatabase() {
|
2018-06-26 10:24:19 +02:00
|
|
|
getWritableDatabase().execSQL("delete from " + KeyRingsPublicModel.TABLE_NAME);
|
|
|
|
|
getWritableDatabase().execSQL("delete from " + ApiAllowedKeysModel.TABLE_NAME);
|
2018-06-15 19:46:55 +02:00
|
|
|
getWritableDatabase().execSQL("delete from api_apps");
|
2014-08-14 17:10:08 +02:00
|
|
|
}
|
|
|
|
|
|
2012-09-18 18:35:14 +02:00
|
|
|
}
|