use SQLDelight, remove ApiApps access from KeychainProvider

This commit is contained in:
Vincent Breitmoser
2018-06-15 19:46:55 +02:00
parent 59c9f52e85
commit d133b732e5
30 changed files with 628 additions and 962 deletions

View File

@@ -20,179 +20,97 @@ package org.sufficientlysecure.keychain.provider;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.arch.persistence.db.SupportSQLiteDatabase;
import android.content.Context;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAllowedKeys;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
import org.sufficientlysecure.keychain.remote.AppSettings;
import com.squareup.sqldelight.SqlDelightQuery;
import org.sufficientlysecure.keychain.ApiAllowedKeysModel.InsertAllowedKey;
import org.sufficientlysecure.keychain.ApiAppsModel;
import org.sufficientlysecure.keychain.ApiAppsModel.DeleteByPackageName;
import org.sufficientlysecure.keychain.ApiAppsModel.InsertApiApp;
import org.sufficientlysecure.keychain.model.ApiAllowedKey;
import org.sufficientlysecure.keychain.model.ApiApp;
public class ApiDataAccessObject {
private final SimpleContentResolverInterface mQueryInterface;
private final SupportSQLiteDatabase db;
public ApiDataAccessObject(Context context) {
final ContentResolver contentResolver = context.getContentResolver();
mQueryInterface = new SimpleContentResolverInterface() {
@Override
public Cursor query(Uri contentUri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
return contentResolver.query(contentUri, projection, selection, selectionArgs, sortOrder);
}
@Override
public Uri insert(Uri contentUri, ContentValues values) {
return contentResolver.insert(contentUri, values);
}
@Override
public int update(Uri contentUri, ContentValues values, String where, String[] selectionArgs) {
return contentResolver.update(contentUri, values, where, selectionArgs);
}
@Override
public int delete(Uri contentUri, String where, String[] selectionArgs) {
return contentResolver.delete(contentUri, where, selectionArgs);
}
};
KeychainDatabase keychainDatabase = new KeychainDatabase(context);
db = keychainDatabase.getWritableDatabase();
}
public ApiDataAccessObject(SimpleContentResolverInterface queryInterface) {
mQueryInterface = queryInterface;
}
public ArrayList<String> getRegisteredApiApps() {
Cursor cursor = mQueryInterface.query(ApiApps.CONTENT_URI, null, null, null, null);
ArrayList<String> packageNames = new ArrayList<>();
try {
if (cursor != null) {
int packageNameCol = cursor.getColumnIndex(ApiApps.PACKAGE_NAME);
if (cursor.moveToFirst()) {
do {
packageNames.add(cursor.getString(packageNameCol));
} while (cursor.moveToNext());
}
}
} finally {
if (cursor != null) {
cursor.close();
public ApiApp getApiApp(String packageName) {
try (Cursor cursor = db.query(ApiApp.FACTORY.selectByPackageName(packageName))) {
if (cursor.moveToFirst()) {
return ApiApp.FACTORY.selectByPackageNameMapper().map(cursor);
}
return null;
}
return packageNames;
}
private ContentValues contentValueForApiApps(AppSettings appSettings) {
ContentValues values = new ContentValues();
values.put(ApiApps.PACKAGE_NAME, appSettings.getPackageName());
values.put(ApiApps.PACKAGE_CERTIFICATE, appSettings.getPackageCertificate());
return values;
}
public void insertApiApp(AppSettings appSettings) {
mQueryInterface.insert(ApiApps.CONTENT_URI, contentValueForApiApps(appSettings));
}
public void deleteApiApp(String packageName) {
mQueryInterface.delete(ApiApps.buildByPackageNameUri(packageName), null, null);
}
/**
* Must be an uri pointing to an account
*/
public AppSettings getApiAppSettings(Uri uri) {
AppSettings settings = null;
Cursor cursor = mQueryInterface.query(uri, null, null, null, null);
try {
if (cursor != null && cursor.moveToFirst()) {
settings = new AppSettings();
settings.setPackageName(cursor.getString(
cursor.getColumnIndex(ApiApps.PACKAGE_NAME)));
settings.setPackageCertificate(cursor.getBlob(
cursor.getColumnIndex(ApiApps.PACKAGE_CERTIFICATE)));
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return settings;
}
public HashSet<Long> getAllowedKeyIdsForApp(Uri uri) {
HashSet<Long> keyIds = new HashSet<>();
Cursor cursor = mQueryInterface.query(uri, null, null, null, null);
try {
if (cursor != null) {
int keyIdColumn = cursor.getColumnIndex(ApiAllowedKeys.KEY_ID);
while (cursor.moveToNext()) {
keyIds.add(cursor.getLong(keyIdColumn));
}
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return keyIds;
}
public void saveAllowedKeyIdsForApp(Uri uri, Set<Long> allowedKeyIds)
throws RemoteException, OperationApplicationException {
// wipe whole table of allowed keys for this account
mQueryInterface.delete(uri, null, null);
// re-insert allowed key ids
for (Long keyId : allowedKeyIds) {
ContentValues values = new ContentValues();
values.put(ApiAllowedKeys.KEY_ID, keyId);
mQueryInterface.insert(uri, values);
}
}
public void addAllowedKeyIdForApp(Uri uri, long allowedKeyId) {
ContentValues values = new ContentValues();
values.put(ApiAllowedKeys.KEY_ID, allowedKeyId);
mQueryInterface.insert(uri, values);
}
public void addAllowedKeyIdForApp(String packageName, long allowedKeyId) {
Uri uri = ApiAllowedKeys.buildBaseUri(packageName);
addAllowedKeyIdForApp(uri, allowedKeyId);
}
public byte[] getApiAppCertificate(String packageName) {
Uri queryUri = ApiApps.buildByPackageNameUri(packageName);
Cursor cursor = db.query(ApiApp.FACTORY.getCertificate(packageName));
return ApiApp.FACTORY.getCertificateMapper().map(cursor);
}
String[] projection = new String[]{ApiApps.PACKAGE_CERTIFICATE};
public void insertApiApp(ApiApp apiApp) {
InsertApiApp statement = new ApiAppsModel.InsertApiApp(db);
statement.bind(apiApp.package_name(), apiApp.package_signature());
statement.execute();
}
Cursor cursor = mQueryInterface.query(queryUri, projection, null, null, null);
try {
byte[] signature = null;
if (cursor != null && cursor.moveToFirst()) {
int signatureCol = 0;
public void deleteApiApp(String packageName) {
DeleteByPackageName deleteByPackageName = new DeleteByPackageName(db);
deleteByPackageName.bind(packageName);
deleteByPackageName.executeUpdateDelete();
}
signature = cursor.getBlob(signatureCol);
}
return signature;
} finally {
if (cursor != null) {
cursor.close();
public HashSet<Long> getAllowedKeyIdsForApp(String packageName) {
SqlDelightQuery allowedKeys = ApiAllowedKey.FACTORY.getAllowedKeys(packageName);
HashSet<Long> keyIds = new HashSet<>();
try (Cursor cursor = db.query(allowedKeys)) {
while (cursor.moveToNext()) {
long allowedKeyId = ApiAllowedKey.FACTORY.getAllowedKeysMapper().map(cursor);
keyIds.add(allowedKeyId);
}
}
return keyIds;
}
public void saveAllowedKeyIdsForApp(String packageName, Set<Long> allowedKeyIds) {
ApiAllowedKey.DeleteByPackageName deleteByPackageName = new ApiAllowedKey.DeleteByPackageName(db);
deleteByPackageName.bind(packageName);
deleteByPackageName.executeUpdateDelete();
InsertAllowedKey statement = new InsertAllowedKey(db);
for (Long keyId : allowedKeyIds) {
statement.bind(packageName, keyId);
statement.execute();
}
}
public void addAllowedKeyIdForApp(String packageName, long allowedKeyId) {
InsertAllowedKey statement = new InsertAllowedKey(db);
statement.bind(packageName, allowedKeyId);
statement.execute();
}
public List<ApiApp> getAllApiApps() {
SqlDelightQuery query = ApiApp.FACTORY.selectAll();
ArrayList<ApiApp> result = new ArrayList<>();
try (Cursor cursor = db.query(query)) {
while (cursor.moveToNext()) {
ApiApp apiApp = ApiApp.FACTORY.selectAllMapper().map(cursor);
result.add(apiApp);
}
}
return result;
}
}

View File

@@ -333,7 +333,7 @@ public class KeyRepository {
try {
return localSecretKeyStorage.readSecretKey(masterKeyId);
} catch (IOException e) {
Timber.e(e, "Error reading public key from storage!");
Timber.e(e, "Error reading secret key from storage!");
throw new NotFoundException();
}
}

View File

@@ -141,9 +141,6 @@ public class KeychainContract {
public static final String PATH_KEYS = "keys";
public static final String PATH_CERTS = "certs";
public static final String BASE_API_APPS = "api_apps";
public static final String PATH_ALLOWED_KEYS = "allowed_keys";
public static final String PATH_BY_PACKAGE_NAME = "by_package_name";
public static final String PATH_BY_KEY_ID = "by_key_id";
@@ -323,43 +320,6 @@ public class KeychainContract {
}
public static class ApiApps implements ApiAppsColumns, BaseColumns {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_API_APPS).build();
/**
* Use if multiple items get returned
*/
public static final String CONTENT_TYPE
= "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.api_apps";
/**
* Use if a single item is returned
*/
public static final String CONTENT_ITEM_TYPE
= "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.provider.api_apps";
public static Uri buildByPackageNameUri(String packageName) {
return CONTENT_URI.buildUpon().appendEncodedPath(packageName).build();
}
}
public static class ApiAllowedKeys implements ApiAppsAllowedKeysColumns, BaseColumns {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_API_APPS).build();
/**
* Use if multiple items get returned
*/
public static final String CONTENT_TYPE
= "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.api_apps.allowed_keys";
public static Uri buildBaseUri(String packageName) {
return CONTENT_URI.buildUpon().appendEncodedPath(packageName).appendPath(PATH_ALLOWED_KEYS)
.build();
}
}
public static class ApiAutocryptPeer implements ApiAutocryptPeerColumns, BaseColumns {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_AUTOCRYPT_PEERS).build();

View File

@@ -23,16 +23,18 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
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;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.provider.BaseColumns;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsAllowedKeysColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAutocryptPeerColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.CertsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns;
@@ -53,10 +55,11 @@ import timber.log.Timber;
* - 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.
*/
public class KeychainDatabase extends SQLiteOpenHelper {
public class KeychainDatabase {
private static final String DATABASE_NAME = "openkeychain.db";
private static final int DATABASE_VERSION = 26;
private Context mContext;
private final SupportSQLiteOpenHelper supportSQLiteOpenHelper;
private Context context;
public interface Tables {
String KEY_RINGS_PUBLIC = "keyrings_public";
@@ -65,18 +68,11 @@ public class KeychainDatabase extends SQLiteOpenHelper {
String KEY_SIGNATURES = "key_signatures";
String USER_PACKETS = "user_packets";
String CERTS = "certs";
String API_APPS = "api_apps";
String API_ALLOWED_KEYS = "api_allowed_keys";
String OVERRIDDEN_WARNINGS = "overridden_warnings";
String API_AUTOCRYPT_PEERS = "api_autocrypt_peers";
}
private static final String CREATE_KEYRINGS_PUBLIC =
"CREATE TABLE IF NOT EXISTS keyrings_public ("
+ KeyRingsColumns.MASTER_KEY_ID + " INTEGER PRIMARY KEY,"
+ KeyRingsColumns.KEY_RING_DATA + " BLOB"
+ ")";
private static final String CREATE_KEYS =
"CREATE TABLE IF NOT EXISTS " + Tables.KEYS + " ("
+ KeysColumns.MASTER_KEY_ID + " INTEGER, "
@@ -143,15 +139,6 @@ public class KeychainDatabase extends SQLiteOpenHelper {
+ Tables.USER_PACKETS + "(" + UserPacketsColumns.MASTER_KEY_ID + ", " + UserPacketsColumns.RANK + ") ON DELETE CASCADE"
+ ")";
private static final String CREATE_UPDATE_KEYS =
"CREATE TABLE IF NOT EXISTS " + Tables.UPDATED_KEYS + " ("
+ UpdatedKeysColumns.MASTER_KEY_ID + " INTEGER PRIMARY KEY, "
+ UpdatedKeysColumns.LAST_UPDATED + " INTEGER, "
+ UpdatedKeysColumns.SEEN_ON_KEYSERVERS + " INTEGER, "
+ "FOREIGN KEY(" + UpdatedKeysColumns.MASTER_KEY_ID + ") REFERENCES "
+ Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE"
+ ")";
private static final String CREATE_KEY_SIGNATURES =
"CREATE TABLE IF NOT EXISTS " + Tables.KEY_SIGNATURES + " ("
+ KeySignaturesColumns.MASTER_KEY_ID + " INTEGER NOT NULL, "
@@ -175,16 +162,9 @@ public class KeychainDatabase extends SQLiteOpenHelper {
+ "PRIMARY KEY(" + ApiAutocryptPeerColumns.PACKAGE_NAME + ", "
+ ApiAutocryptPeerColumns.IDENTIFIER + "), "
+ "FOREIGN KEY(" + ApiAutocryptPeerColumns.PACKAGE_NAME + ") REFERENCES "
+ Tables.API_APPS + "(" + ApiAppsColumns.PACKAGE_NAME + ") ON DELETE CASCADE"
+ "api_apps (package_signature) ON DELETE CASCADE"
+ ")";
private static final String CREATE_API_APPS =
"CREATE TABLE IF NOT EXISTS " + Tables.API_APPS + " ("
+ BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ ApiAppsColumns.PACKAGE_NAME + " TEXT NOT NULL UNIQUE, "
+ ApiAppsColumns.PACKAGE_CERTIFICATE + " BLOB"
+ ")";
private static final String CREATE_API_APPS_ALLOWED_KEYS =
"CREATE TABLE IF NOT EXISTS " + Tables.API_ALLOWED_KEYS + " ("
+ BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
@@ -194,7 +174,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
+ "UNIQUE(" + ApiAppsAllowedKeysColumns.KEY_ID + ", "
+ ApiAppsAllowedKeysColumns.PACKAGE_NAME + "), "
+ "FOREIGN KEY(" + ApiAppsAllowedKeysColumns.PACKAGE_NAME + ") REFERENCES "
+ Tables.API_APPS + "(" + ApiAppsAllowedKeysColumns.PACKAGE_NAME + ") ON DELETE CASCADE"
+ "api_apps (" + ApiAppsAllowedKeysColumns.PACKAGE_NAME + ") ON DELETE CASCADE"
+ ")";
private static final String CREATE_OVERRIDDEN_WARNINGS =
@@ -204,12 +184,46 @@ public class KeychainDatabase extends SQLiteOpenHelper {
+ ")";
public KeychainDatabase(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
mContext = context;
this.context = context;
supportSQLiteOpenHelper =
new FrameworkSQLiteOpenHelperFactory()
.create(Configuration.builder(context).name(DATABASE_NAME).callback(
new Callback(DATABASE_VERSION) {
@Override
public void onCreate(SupportSQLiteDatabase db) {
KeychainDatabase.this.onCreate(db);
}
@Override
public void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
KeychainDatabase.this.onUpgrade(db, oldVersion, newVersion);
}
@Override
public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
KeychainDatabase.this.onDowngrade(db, oldVersion, newVersion);
}
@Override
public void onOpen(SupportSQLiteDatabase db) {
super.onOpen(db);
if (!db.isReadOnly()) {
// Enable foreign key constraints
db.execSQL("PRAGMA foreign_keys=ON;");
}
}
}).build());
}
@Override
public void onCreate(SQLiteDatabase db) {
public SupportSQLiteDatabase getReadableDatabase() {
return supportSQLiteOpenHelper.getReadableDatabase();
}
public SupportSQLiteDatabase getWritableDatabase() {
return supportSQLiteOpenHelper.getWritableDatabase();
}
private void onCreate(SupportSQLiteDatabase db) {
Timber.w("Creating database...");
db.execSQL(CREATE_KEYRINGS_PUBLIC);
@@ -218,7 +232,6 @@ public class KeychainDatabase extends SQLiteOpenHelper {
db.execSQL(CREATE_CERTS);
db.execSQL(CREATE_UPDATE_KEYS);
db.execSQL(CREATE_KEY_SIGNATURES);
db.execSQL(CREATE_API_APPS);
db.execSQL(CREATE_API_APPS_ALLOWED_KEYS);
db.execSQL(CREATE_OVERRIDDEN_WARNINGS);
db.execSQL(CREATE_API_AUTOCRYPT_PEERS);
@@ -231,20 +244,10 @@ public class KeychainDatabase extends SQLiteOpenHelper {
db.execSQL("CREATE INDEX uids_by_email ON user_packets ("
+ UserPacketsColumns.EMAIL + ");");
Preferences.getPreferences(mContext).setKeySignaturesTableInitialized();
Preferences.getPreferences(context).setKeySignaturesTableInitialized();
}
@Override
public void onOpen(SQLiteDatabase db) {
super.onOpen(db);
if (!db.isReadOnly()) {
// Enable foreign key constraints
db.execSQL("PRAGMA foreign_keys=ON;");
}
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
private void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
Timber.d("Upgrading db from " + oldVersion + " to " + newVersion);
switch (oldVersion) {
@@ -459,9 +462,9 @@ public class KeychainDatabase extends SQLiteOpenHelper {
}
}
private void migrateSecretKeysFromDbToLocalStorage(SQLiteDatabase db) throws IOException {
LocalSecretKeyStorage localSecretKeyStorage = LocalSecretKeyStorage.getInstance(mContext);
Cursor cursor = db.rawQuery("SELECT master_key_id, key_ring_data FROM keyrings_secret", null);
private void migrateSecretKeysFromDbToLocalStorage(SupportSQLiteDatabase db) throws IOException {
LocalSecretKeyStorage localSecretKeyStorage = LocalSecretKeyStorage.getInstance(context);
Cursor cursor = db.query("SELECT master_key_id, key_ring_data FROM keyrings_secret");
while (cursor.moveToNext()) {
long masterKeyId = cursor.getLong(0);
byte[] secretKeyBlob = cursor.getBlob(1);
@@ -473,8 +476,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
// db.execSQL("DROP TABLE keyrings_secret");
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
// Downgrade is ok for the debug version, makes it easier to work with branches
if (Constants.DEBUG) {
return;
@@ -528,7 +530,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
public void clearDatabase() {
getWritableDatabase().execSQL("delete from " + Tables.KEY_RINGS_PUBLIC);
getWritableDatabase().execSQL("delete from " + Tables.API_ALLOWED_KEYS);
getWritableDatabase().execSQL("delete from " + Tables.API_APPS);
getWritableDatabase().execSQL("delete from api_apps");
}
}

View File

@@ -23,6 +23,7 @@ import java.util.Date;
import java.util.HashMap;
import java.util.List;
import android.arch.persistence.db.SupportSQLiteDatabase;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
@@ -38,8 +39,6 @@ import android.text.TextUtils;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAllowedKeys;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAutocryptPeer;
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
@@ -71,10 +70,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
private static final int KEY_RING_LINKED_IDS = 207;
private static final int KEY_RING_LINKED_ID_CERTS = 208;
private static final int API_APPS = 301;
private static final int API_APPS_BY_PACKAGE_NAME = 302;
private static final int API_ALLOWED_KEYS = 305;
private static final int KEY_RINGS_FIND_BY_EMAIL = 400;
private static final int KEY_RINGS_FIND_BY_SUBKEY = 401;
private static final int KEY_RINGS_FIND_BY_USER_ID = 402;
@@ -182,22 +177,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
+ KeychainContract.PATH_CERTS + "/*/*",
KEY_RING_CERTS_SPECIFIC);
/*
* API apps
*
* <pre>
* api_apps
* api_apps/_ (package name)
*
* api_apps/_/allowed_keys
* </pre>
*/
matcher.addURI(authority, KeychainContract.BASE_API_APPS, API_APPS);
matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/*", API_APPS_BY_PACKAGE_NAME);
matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/*/"
+ KeychainContract.PATH_ALLOWED_KEYS, API_ALLOWED_KEYS);
/*
* Trust Identity access
*
@@ -269,15 +248,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
case KEY_SIGNATURES:
return KeySignatures.CONTENT_TYPE;
case API_APPS:
return ApiApps.CONTENT_TYPE;
case API_APPS_BY_PACKAGE_NAME:
return ApiApps.CONTENT_ITEM_TYPE;
case API_ALLOWED_KEYS:
return ApiAllowedKeys.CONTENT_TYPE;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
@@ -781,26 +751,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
}
break;
}
case API_APPS: {
qb.setTables(Tables.API_APPS);
break;
}
case API_APPS_BY_PACKAGE_NAME: {
qb.setTables(Tables.API_APPS);
qb.appendWhere(ApiApps.PACKAGE_NAME + " = ");
qb.appendWhereEscapeString(uri.getLastPathSegment());
break;
}
case API_ALLOWED_KEYS: {
qb.setTables(Tables.API_ALLOWED_KEYS);
qb.appendWhere(Tables.API_ALLOWED_KEYS + "." + ApiAllowedKeys.PACKAGE_NAME + " = ");
qb.appendWhereEscapeString(uri.getPathSegments().get(1));
break;
}
default: {
throw new IllegalArgumentException("Unknown URI " + uri + " (" + match + ")");
}
@@ -815,9 +765,10 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
orderBy = sortOrder;
}
SQLiteDatabase db = getDb().getReadableDatabase();
SupportSQLiteDatabase db = getDb().getReadableDatabase();
Cursor cursor = qb.query(db, projection, selection, selectionArgs, groupBy, null, orderBy);
String query = qb.buildQuery(projection, selection, groupBy, null, orderBy, null);
Cursor cursor = db.query(query, selectionArgs);
if (cursor != null) {
// Tell the cursor what uri to watch, so it knows when its source data changes
cursor.setNotificationUri(getContext().getContentResolver(), uri);
@@ -844,7 +795,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
public Uri insert(Uri uri, ContentValues values) {
Timber.d("insert(uri=" + uri + ", values=" + values.toString() + ")");
final SQLiteDatabase db = getDb().getWritableDatabase();
final SupportSQLiteDatabase db = getDb().getWritableDatabase();
Uri rowUri = null;
Long keyId = null;
@@ -853,12 +804,12 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
switch (match) {
case KEY_RING_PUBLIC: {
db.insertOrThrow(Tables.KEY_RINGS_PUBLIC, null, values);
db.insert(Tables.KEY_RINGS_PUBLIC, SQLiteDatabase.CONFLICT_FAIL, values);
keyId = values.getAsLong(KeyRings.MASTER_KEY_ID);
break;
}
case KEY_RING_KEYS: {
db.insertOrThrow(Tables.KEYS, null, values);
db.insert(Tables.KEYS, SQLiteDatabase.CONFLICT_FAIL, values);
keyId = values.getAsLong(Keys.MASTER_KEY_ID);
break;
}
@@ -873,46 +824,33 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
if (((Number) values.get(UserPacketsColumns.RANK)).intValue() == 0 && values.get(UserPacketsColumns.USER_ID) == null) {
throw new AssertionError("Rank 0 user packet must be a user id!");
}
db.insertOrThrow(Tables.USER_PACKETS, null, values);
db.insert(Tables.USER_PACKETS, SQLiteDatabase.CONFLICT_FAIL, values);
keyId = values.getAsLong(UserPackets.MASTER_KEY_ID);
break;
}
case KEY_RING_CERTS: {
// we replace here, keeping only the latest signature
// TODO this would be better handled in savePublicKeyRing directly!
db.replaceOrThrow(Tables.CERTS, null, values);
db.insert(Tables.CERTS, SQLiteDatabase.CONFLICT_FAIL, values);
keyId = values.getAsLong(Certs.MASTER_KEY_ID);
break;
}
case UPDATED_KEYS: {
keyId = values.getAsLong(UpdatedKeys.MASTER_KEY_ID);
try {
db.insertOrThrow(Tables.UPDATED_KEYS, null, values);
db.insert(Tables.UPDATED_KEYS, SQLiteDatabase.CONFLICT_FAIL, values);
} catch (SQLiteConstraintException e) {
db.update(Tables.UPDATED_KEYS, values,
db.update(Tables.UPDATED_KEYS, SQLiteDatabase.CONFLICT_IGNORE, values,
UpdatedKeys.MASTER_KEY_ID + " = ?", new String[] { Long.toString(keyId) });
}
rowUri = UpdatedKeys.CONTENT_URI;
break;
}
case KEY_SIGNATURES: {
db.insert(Tables.KEY_SIGNATURES, null, values);
db.insert(Tables.KEY_SIGNATURES, SQLiteDatabase.CONFLICT_FAIL, values);
rowUri = KeySignatures.CONTENT_URI;
break;
}
case API_APPS: {
db.insert(Tables.API_APPS, null, values);
break;
}
case API_ALLOWED_KEYS: {
// set foreign key automatically based on given uri
// e.g., api_apps/com.example.app/allowed_keys/
String packageName = uri.getPathSegments().get(1);
values.put(ApiAllowedKeys.PACKAGE_NAME, packageName);
db.insert(Tables.API_ALLOWED_KEYS, null, values);
break;
}
default: {
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
@@ -937,7 +875,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
public int delete(Uri uri, String additionalSelection, String[] selectionArgs) {
Timber.v("delete(uri=" + uri + ")");
final SQLiteDatabase db = getDb().getWritableDatabase();
final SupportSQLiteDatabase db = getDb().getWritableDatabase();
int count;
final int match = mUriMatcher.match(uri);
@@ -980,16 +918,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
count = db.delete(Tables.API_AUTOCRYPT_PEERS, selection, selectionArgs);
break;
case API_APPS_BY_PACKAGE_NAME: {
count = db.delete(Tables.API_APPS, buildDefaultApiAppsSelection(uri, additionalSelection),
selectionArgs);
break;
}
case API_ALLOWED_KEYS: {
count = db.delete(Tables.API_ALLOWED_KEYS, buildDefaultApiAllowedKeysSelection(uri, additionalSelection),
selectionArgs);
break;
}
default: {
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
@@ -1005,7 +933,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
Timber.v("update(uri=" + uri + ", values=" + values.toString() + ")");
final SQLiteDatabase db = getDb().getWritableDatabase();
final SupportSQLiteDatabase db = getDb().getWritableDatabase();
int count = 0;
try {
@@ -1022,12 +950,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
if (!TextUtils.isEmpty(selection)) {
actualSelection += " AND (" + selection + ")";
}
count = db.update(Tables.KEYS, values, actualSelection, selectionArgs);
break;
}
case API_APPS_BY_PACKAGE_NAME: {
count = db.update(Tables.API_APPS, values,
buildDefaultApiAppsSelection(uri, selection), selectionArgs);
count = db.update(Tables.KEYS, SQLiteDatabase.CONFLICT_FAIL, values, actualSelection, selectionArgs);
break;
}
case UPDATED_KEYS: {
@@ -1040,7 +963,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
throw new UnsupportedOperationException("can only reset all keys");
}
db.update(Tables.UPDATED_KEYS, values, null, null);
db.update(Tables.UPDATED_KEYS, SQLiteDatabase.CONFLICT_FAIL, values, null, null);
break;
}
case AUTOCRYPT_PEERS_BY_PACKAGE_NAME_AND_TRUST_ID: {
@@ -1049,11 +972,11 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
values.put(ApiAutocryptPeer.PACKAGE_NAME, packageName);
values.put(ApiAutocryptPeer.IDENTIFIER, identifier);
int updated = db.update(Tables.API_AUTOCRYPT_PEERS, values,
int updated = db.update(Tables.API_AUTOCRYPT_PEERS, SQLiteDatabase.CONFLICT_IGNORE, values,
ApiAutocryptPeer.PACKAGE_NAME + "=? AND " + ApiAutocryptPeer.IDENTIFIER + "=?",
new String[] { packageName, identifier });
if (updated == 0) {
db.insertOrThrow(Tables.API_AUTOCRYPT_PEERS, null, values);
db.insert(Tables.API_AUTOCRYPT_PEERS, SQLiteDatabase.CONFLICT_FAIL,values);
}
break;
@@ -1068,35 +991,4 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
return count;
}
/**
* Build default selection statement for API apps. If no extra selection is specified only build
* where clause with rowId
*
* @param uri
* @param selection
* @return
*/
private String buildDefaultApiAppsSelection(Uri uri, String selection) {
String packageName = DatabaseUtils.sqlEscapeString(uri.getLastPathSegment());
String andSelection = "";
if (!TextUtils.isEmpty(selection)) {
andSelection = " AND (" + selection + ")";
}
return ApiApps.PACKAGE_NAME + "=" + packageName + andSelection;
}
private String buildDefaultApiAllowedKeysSelection(Uri uri, String selection) {
String packageName = DatabaseUtils.sqlEscapeString(uri.getPathSegments().get(1));
String andSelection = "";
if (!TextUtils.isEmpty(selection)) {
andSelection = " AND (" + selection + ")";
}
return ApiAllowedKeys.PACKAGE_NAME + "=" + packageName + andSelection;
}
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright (C) 2017 Schürmann & Breitmoser GbR
*
* 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.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.provider;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import android.content.Context;
import okhttp3.internal.Util;
class LocalSecretKeyStorage {
private static final String FORMAT_STR_SECRET_KEY = "0x%016x.sec";
private static final String SECRET_KEYS_DIR_NAME = "secret_keys";
private final File localSecretKeysDir;
public static LocalSecretKeyStorage getInstance(Context context) {
File localSecretKeysDir = new File(context.getFilesDir(), SECRET_KEYS_DIR_NAME);
return new LocalSecretKeyStorage(localSecretKeysDir);
}
private LocalSecretKeyStorage(File localSecretKeysDir) {
this.localSecretKeysDir = localSecretKeysDir;
}
private File getSecretKeyFile(long masterKeyId) throws IOException {
if (!localSecretKeysDir.exists()) {
localSecretKeysDir.mkdir();
}
if (!localSecretKeysDir.isDirectory()) {
throw new IOException("Failed creating public key directory!");
}
String keyFilename = String.format(FORMAT_STR_SECRET_KEY, masterKeyId);
return new File(localSecretKeysDir, keyFilename);
}
void writeSecretKey(long masterKeyId, byte[] encoded) throws IOException {
File publicKeyFile = getSecretKeyFile(masterKeyId);
FileOutputStream fileOutputStream = new FileOutputStream(publicKeyFile);
try {
fileOutputStream.write(encoded);
} finally {
Util.closeQuietly(fileOutputStream);
}
}
byte[] readSecretKey(long masterKeyId) throws IOException {
File publicKeyFile = getSecretKeyFile(masterKeyId);
try {
FileInputStream fileInputStream = new FileInputStream(publicKeyFile);
return readIntoByteArray(fileInputStream);
} catch (FileNotFoundException e) {
return null;
}
}
private static byte[] readIntoByteArray(FileInputStream fileInputStream) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[128];
int bytesRead;
while ((bytesRead = fileInputStream.read(buf)) != -1) {
baos.write(buf, 0, bytesRead);
}
return baos.toByteArray();
}
void deleteSecretKey(long masterKeyId) throws IOException {
File publicKeyFile = getSecretKeyFile(masterKeyId);
if (publicKeyFile.exists()) {
boolean deleteSuccess = publicKeyFile.delete();
if (!deleteSuccess) {
throw new IOException("File exists, but could not be deleted!");
}
}
}
}

View File

@@ -18,6 +18,9 @@
package org.sufficientlysecure.keychain.provider;
import android.arch.persistence.db.SupportSQLiteDatabase;
import android.arch.persistence.db.SupportSQLiteQuery;
import android.arch.persistence.db.SupportSQLiteQueryBuilder;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
@@ -47,34 +50,31 @@ public class OverriddenWarningsRepository {
}
public boolean isWarningOverridden(String identifier) {
SQLiteDatabase db = getDb().getReadableDatabase();
Cursor cursor = db.query(
Tables.OVERRIDDEN_WARNINGS,
new String[] { "COUNT(*)" },
OverriddenWarnings.IDENTIFIER + " = ?",
new String[] { identifier },
null, null, null);
SupportSQLiteDatabase db = getDb().getReadableDatabase();
SupportSQLiteQuery query = SupportSQLiteQueryBuilder
.builder(Tables.OVERRIDDEN_WARNINGS)
.columns(new String[] { "COUNT(*) FROM " })
.selection(OverriddenWarnings.IDENTIFIER + " = ?", new String[] { identifier })
.create();
Cursor cursor = db.query(query);
try {
cursor.moveToFirst();
return cursor.getInt(0) > 0;
} finally {
cursor.close();
db.close();
}
}
public void putOverride(String identifier) {
SQLiteDatabase db = getDb().getWritableDatabase();
SupportSQLiteDatabase db = getDb().getWritableDatabase();
ContentValues cv = new ContentValues();
cv.put(OverriddenWarnings.IDENTIFIER, identifier);
db.replace(Tables.OVERRIDDEN_WARNINGS, null, cv);
db.close();
db.insert(Tables.OVERRIDDEN_WARNINGS, SQLiteDatabase.CONFLICT_REPLACE, cv);
}
public void deleteOverride(String identifier) {
SQLiteDatabase db = getDb().getWritableDatabase();
SupportSQLiteDatabase db = getDb().getWritableDatabase();
db.delete(Tables.OVERRIDDEN_WARNINGS, OverriddenWarnings.IDENTIFIER + " = ?", new String[] { identifier });
db.close();
}
}