clean up package structure
This commit is contained in:
@@ -1,66 +0,0 @@
|
||||
package org.sufficientlysecure.keychain.provider;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import android.arch.persistence.db.SupportSQLiteDatabase;
|
||||
import android.arch.persistence.db.SupportSQLiteQuery;
|
||||
import android.database.Cursor;
|
||||
|
||||
import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException;
|
||||
|
||||
|
||||
class AbstractDao {
|
||||
private final KeychainDatabase db;
|
||||
private final DatabaseNotifyManager databaseNotifyManager;
|
||||
|
||||
AbstractDao(KeychainDatabase db, DatabaseNotifyManager databaseNotifyManager) {
|
||||
this.db = db;
|
||||
this.databaseNotifyManager = databaseNotifyManager;
|
||||
}
|
||||
|
||||
SupportSQLiteDatabase getReadableDb() {
|
||||
return db.getReadableDatabase();
|
||||
}
|
||||
|
||||
SupportSQLiteDatabase getWritableDb() {
|
||||
return db.getWritableDatabase();
|
||||
}
|
||||
|
||||
DatabaseNotifyManager getDatabaseNotifyManager() {
|
||||
return databaseNotifyManager;
|
||||
}
|
||||
|
||||
<T> List<T> mapAllRows(SupportSQLiteQuery query, Mapper<T> mapper) {
|
||||
ArrayList<T> result = new ArrayList<>();
|
||||
try (Cursor cursor = getReadableDb().query(query)) {
|
||||
while (cursor.moveToNext()) {
|
||||
T item = mapper.map(cursor);
|
||||
result.add(item);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
<T> T mapSingleRowOrThrow(SupportSQLiteQuery query, Mapper<T> mapper) throws NotFoundException {
|
||||
T result = mapSingleRow(query, mapper);
|
||||
if (result == null) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
<T> T mapSingleRow(SupportSQLiteQuery query, Mapper<T> mapper) {
|
||||
try (Cursor cursor = getReadableDb().query(query)) {
|
||||
if (cursor.moveToNext()) {
|
||||
return mapper.map(cursor);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
interface Mapper<T> {
|
||||
T map(Cursor cursor);
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
/*
|
||||
* 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.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
|
||||
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 ApiAppDao extends AbstractDao {
|
||||
public static ApiAppDao getInstance(Context context) {
|
||||
KeychainDatabase keychainDatabase = KeychainDatabase.getInstance(context);
|
||||
DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context);
|
||||
|
||||
return new ApiAppDao(keychainDatabase, databaseNotifyManager);
|
||||
}
|
||||
|
||||
private ApiAppDao(KeychainDatabase keychainDatabase, DatabaseNotifyManager databaseNotifyManager) {
|
||||
super(keychainDatabase, databaseNotifyManager);
|
||||
}
|
||||
|
||||
public ApiApp getApiApp(String packageName) {
|
||||
try (Cursor cursor = getReadableDb().query(ApiApp.FACTORY.selectByPackageName(packageName))) {
|
||||
if (cursor.moveToFirst()) {
|
||||
return ApiApp.FACTORY.selectByPackageNameMapper().map(cursor);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getApiAppCertificate(String packageName) {
|
||||
try (Cursor cursor = getReadableDb().query(ApiApp.FACTORY.getCertificate(packageName))) {
|
||||
if (cursor.moveToFirst()) {
|
||||
return ApiApp.FACTORY.getCertificateMapper().map(cursor);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void insertApiApp(ApiApp apiApp) {
|
||||
ApiApp existingApiApp = getApiApp(apiApp.package_name());
|
||||
if (existingApiApp != null) {
|
||||
if (!Arrays.equals(existingApiApp.package_signature(), apiApp.package_signature())) {
|
||||
throw new IllegalStateException("Inserting existing api with different signature?!");
|
||||
}
|
||||
return;
|
||||
}
|
||||
InsertApiApp statement = new ApiAppsModel.InsertApiApp(getWritableDb());
|
||||
statement.bind(apiApp.package_name(), apiApp.package_signature());
|
||||
statement.executeInsert();
|
||||
}
|
||||
|
||||
public void deleteApiApp(String packageName) {
|
||||
DeleteByPackageName deleteByPackageName = new DeleteByPackageName(getWritableDb());
|
||||
deleteByPackageName.bind(packageName);
|
||||
deleteByPackageName.executeUpdateDelete();
|
||||
}
|
||||
|
||||
public HashSet<Long> getAllowedKeyIdsForApp(String packageName) {
|
||||
SqlDelightQuery allowedKeys = ApiAllowedKey.FACTORY.getAllowedKeys(packageName);
|
||||
HashSet<Long> keyIds = new HashSet<>();
|
||||
try (Cursor cursor = getReadableDb().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(getWritableDb());
|
||||
deleteByPackageName.bind(packageName);
|
||||
deleteByPackageName.executeUpdateDelete();
|
||||
|
||||
InsertAllowedKey statement = new InsertAllowedKey(getWritableDb());
|
||||
for (Long keyId : allowedKeyIds) {
|
||||
statement.bind(packageName, keyId);
|
||||
statement.execute();
|
||||
}
|
||||
}
|
||||
|
||||
public void addAllowedKeyIdForApp(String packageName, long allowedKeyId) {
|
||||
InsertAllowedKey statement = new InsertAllowedKey(getWritableDb());
|
||||
statement.bind(packageName, allowedKeyId);
|
||||
statement.execute();
|
||||
}
|
||||
|
||||
public List<ApiApp> getAllApiApps() {
|
||||
SqlDelightQuery query = ApiApp.FACTORY.selectAll();
|
||||
|
||||
ArrayList<ApiApp> result = new ArrayList<>();
|
||||
try (Cursor cursor = getReadableDb().query(query)) {
|
||||
while (cursor.moveToNext()) {
|
||||
ApiApp apiApp = ApiApp.FACTORY.selectAllMapper().map(cursor);
|
||||
result.add(apiApp);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
/*
|
||||
* 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.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.squareup.sqldelight.SqlDelightQuery;
|
||||
import org.sufficientlysecure.keychain.AutocryptPeersModel.DeleteByIdentifier;
|
||||
import org.sufficientlysecure.keychain.AutocryptPeersModel.DeleteByMasterKeyId;
|
||||
import org.sufficientlysecure.keychain.AutocryptPeersModel.InsertPeer;
|
||||
import org.sufficientlysecure.keychain.AutocryptPeersModel.UpdateGossipKey;
|
||||
import org.sufficientlysecure.keychain.AutocryptPeersModel.UpdateKey;
|
||||
import org.sufficientlysecure.keychain.AutocryptPeersModel.UpdateLastSeen;
|
||||
import org.sufficientlysecure.keychain.model.AutocryptPeer;
|
||||
import org.sufficientlysecure.keychain.model.AutocryptPeer.AutocryptKeyStatus;
|
||||
import org.sufficientlysecure.keychain.model.AutocryptPeer.GossipOrigin;
|
||||
|
||||
|
||||
public class AutocryptPeerDao extends AbstractDao {
|
||||
public static AutocryptPeerDao getInstance(Context context) {
|
||||
KeychainDatabase keychainDatabase = KeychainDatabase.getInstance(context);
|
||||
DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context);
|
||||
|
||||
return new AutocryptPeerDao(keychainDatabase, databaseNotifyManager);
|
||||
}
|
||||
|
||||
private AutocryptPeerDao(KeychainDatabase database, DatabaseNotifyManager databaseNotifyManager) {
|
||||
super(database, databaseNotifyManager);
|
||||
}
|
||||
|
||||
public Long getMasterKeyIdForAutocryptPeer(String autocryptId) {
|
||||
SqlDelightQuery query = AutocryptPeer.FACTORY.selectMasterKeyIdByIdentifier(autocryptId);
|
||||
try (Cursor cursor = getReadableDb().query(query)) {
|
||||
if (cursor.moveToFirst()) {
|
||||
return AutocryptPeer.FACTORY.selectMasterKeyIdByIdentifierMapper().map(cursor);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public AutocryptPeer getAutocryptPeer(String packageName, String autocryptId) {
|
||||
List<AutocryptPeer> autocryptPeers = getAutocryptPeers(packageName, autocryptId);
|
||||
if (!autocryptPeers.isEmpty()) {
|
||||
return autocryptPeers.get(0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<AutocryptPeer> getAutocryptPeers(String packageName, String... autocryptId) {
|
||||
ArrayList<AutocryptPeer> result = new ArrayList<>(autocryptId.length);
|
||||
SqlDelightQuery query = AutocryptPeer.FACTORY.selectByIdentifiers(packageName, autocryptId);
|
||||
try (Cursor cursor = getReadableDb().query(query)) {
|
||||
if (cursor.moveToNext()) {
|
||||
AutocryptPeer autocryptPeer = AutocryptPeer.PEER_MAPPER.map(cursor);
|
||||
result.add(autocryptPeer);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<AutocryptKeyStatus> getAutocryptKeyStatus(String packageName, String[] autocryptIds) {
|
||||
ArrayList<AutocryptKeyStatus> result = new ArrayList<>(autocryptIds.length);
|
||||
SqlDelightQuery query = AutocryptPeer.FACTORY.selectAutocryptKeyStatus(packageName, autocryptIds, System.currentTimeMillis());
|
||||
try (Cursor cursor = getReadableDb().query(query)) {
|
||||
if (cursor.moveToNext()) {
|
||||
AutocryptKeyStatus autocryptPeer = AutocryptPeer.KEY_STATUS_MAPPER.map(cursor);
|
||||
result.add(autocryptPeer);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void insertOrUpdateLastSeen(String packageName, String autocryptId, Date date) {
|
||||
UpdateLastSeen updateStatement = new UpdateLastSeen(getWritableDb(), AutocryptPeer.FACTORY);
|
||||
updateStatement.bind(packageName, autocryptId, date);
|
||||
int updated = updateStatement.executeUpdateDelete();
|
||||
|
||||
if (updated == 0) {
|
||||
InsertPeer insertStatement = new InsertPeer(getWritableDb(), AutocryptPeer.FACTORY);
|
||||
insertStatement.bind(packageName, autocryptId, date);
|
||||
insertStatement.executeInsert();
|
||||
}
|
||||
}
|
||||
|
||||
public void updateKey(String packageName, String autocryptId, Date effectiveDate, long masterKeyId,
|
||||
boolean isMutual) {
|
||||
UpdateKey updateStatement = new UpdateKey(getWritableDb(), AutocryptPeer.FACTORY);
|
||||
updateStatement.bind(packageName, autocryptId, effectiveDate, masterKeyId, isMutual);
|
||||
int rowsUpdated = updateStatement.executeUpdateDelete();
|
||||
if (rowsUpdated == 0) {
|
||||
throw new IllegalStateException("No rows updated! Was this peer inserted before the update?");
|
||||
}
|
||||
getDatabaseNotifyManager().notifyAutocryptUpdate(autocryptId, masterKeyId);
|
||||
}
|
||||
|
||||
public void updateKeyGossip(String packageName, String autocryptId, Date effectiveDate, long masterKeyId,
|
||||
GossipOrigin origin) {
|
||||
UpdateGossipKey updateStatement = new UpdateGossipKey(getWritableDb(), AutocryptPeer.FACTORY);
|
||||
updateStatement.bind(packageName, autocryptId, effectiveDate, masterKeyId, origin);
|
||||
int rowsUpdated = updateStatement.executeUpdateDelete();
|
||||
if (rowsUpdated == 0) {
|
||||
throw new IllegalStateException("No rows updated! Was this peer inserted before the update?");
|
||||
}
|
||||
getDatabaseNotifyManager().notifyAutocryptUpdate(autocryptId, masterKeyId);
|
||||
}
|
||||
|
||||
public List<AutocryptPeer> getAutocryptPeersForKey(long masterKeyId) {
|
||||
ArrayList<AutocryptPeer> result = new ArrayList<>();
|
||||
SqlDelightQuery query = AutocryptPeer.FACTORY.selectByMasterKeyId(masterKeyId);
|
||||
try (Cursor cursor = getReadableDb().query(query)) {
|
||||
if (cursor.moveToNext()) {
|
||||
AutocryptPeer autocryptPeer = AutocryptPeer.PEER_MAPPER.map(cursor);
|
||||
result.add(autocryptPeer);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void deleteByIdentifier(String packageName, String autocryptId) {
|
||||
Long masterKeyId = getMasterKeyIdForAutocryptPeer(autocryptId);
|
||||
DeleteByIdentifier deleteStatement = new DeleteByIdentifier(getReadableDb());
|
||||
deleteStatement.bind(packageName, autocryptId);
|
||||
deleteStatement.execute();
|
||||
if (masterKeyId != null) {
|
||||
getDatabaseNotifyManager().notifyAutocryptDelete(autocryptId, masterKeyId);
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteByMasterKeyId(long masterKeyId) {
|
||||
DeleteByMasterKeyId deleteStatement = new DeleteByMasterKeyId(getReadableDb());
|
||||
deleteStatement.bind(masterKeyId);
|
||||
deleteStatement.execute();
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package org.sufficientlysecure.keychain.provider;
|
||||
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
|
||||
|
||||
public class DatabaseNotifyManager {
|
||||
private static final Uri BASE_URI = Uri.parse("content://" + Constants.PROVIDER_AUTHORITY);
|
||||
|
||||
private ContentResolver contentResolver;
|
||||
|
||||
public static DatabaseNotifyManager create(Context context) {
|
||||
ContentResolver contentResolver = context.getContentResolver();
|
||||
return new DatabaseNotifyManager(contentResolver);
|
||||
}
|
||||
|
||||
private DatabaseNotifyManager(ContentResolver contentResolver) {
|
||||
this.contentResolver = contentResolver;
|
||||
}
|
||||
|
||||
public void notifyKeyChange(long masterKeyId) {
|
||||
Uri uri = getNotifyUriMasterKeyId(masterKeyId);
|
||||
contentResolver.notifyChange(uri, null);
|
||||
}
|
||||
|
||||
public void notifyAutocryptDelete(String autocryptId, Long masterKeyId) {
|
||||
Uri uri = getNotifyUriMasterKeyId(masterKeyId);
|
||||
contentResolver.notifyChange(uri, null);
|
||||
}
|
||||
|
||||
public void notifyAutocryptUpdate(String autocryptId, long masterKeyId) {
|
||||
Uri uri = getNotifyUriMasterKeyId(masterKeyId);
|
||||
contentResolver.notifyChange(uri, null);
|
||||
}
|
||||
|
||||
public void notifyKeyMetadataChange(long masterKeyId) {
|
||||
Uri uri = getNotifyUriMasterKeyId(masterKeyId);
|
||||
contentResolver.notifyChange(uri, null);
|
||||
}
|
||||
|
||||
public static Uri getNotifyUriAllKeys() {
|
||||
return BASE_URI;
|
||||
}
|
||||
|
||||
public static Uri getNotifyUriMasterKeyId(long masterKeyId) {
|
||||
return BASE_URI.buildUpon().appendPath(Long.toString(masterKeyId)).build();
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package org.sufficientlysecure.keychain.provider;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
|
||||
import com.squareup.sqldelight.SqlDelightQuery;
|
||||
import org.sufficientlysecure.keychain.KeyMetadataModel.ReplaceKeyMetadata;
|
||||
import org.sufficientlysecure.keychain.model.KeyMetadata;
|
||||
|
||||
|
||||
public class KeyMetadataDao extends AbstractDao {
|
||||
public static KeyMetadataDao create(Context context) {
|
||||
KeychainDatabase database = KeychainDatabase.getInstance(context);
|
||||
DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context);
|
||||
|
||||
return new KeyMetadataDao(database, databaseNotifyManager);
|
||||
}
|
||||
|
||||
private KeyMetadataDao(KeychainDatabase database, DatabaseNotifyManager databaseNotifyManager) {
|
||||
super(database, databaseNotifyManager);
|
||||
}
|
||||
|
||||
public KeyMetadata getKeyMetadata(long masterKeyId) {
|
||||
SqlDelightQuery query = KeyMetadata.FACTORY.selectByMasterKeyId(masterKeyId);
|
||||
try (Cursor cursor = getReadableDb().query(query)) {
|
||||
if (cursor.moveToFirst()) {
|
||||
return KeyMetadata.FACTORY.selectByMasterKeyIdMapper().map(cursor);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void resetAllLastUpdatedTimes() {
|
||||
new KeyMetadata.DeleteAllLastUpdatedTimes(getWritableDb()).execute();
|
||||
}
|
||||
|
||||
public void renewKeyLastUpdatedTime(long masterKeyId, boolean seenOnKeyservers) {
|
||||
ReplaceKeyMetadata replaceStatement = new ReplaceKeyMetadata(getWritableDb(), KeyMetadata.FACTORY);
|
||||
replaceStatement.bind(masterKeyId, new Date(), seenOnKeyservers);
|
||||
replaceStatement.executeInsert();
|
||||
|
||||
getDatabaseNotifyManager().notifyKeyMetadataChange(masterKeyId);
|
||||
}
|
||||
|
||||
public List<byte[]> getFingerprintsForKeysOlderThan(long olderThan, TimeUnit timeUnit) {
|
||||
SqlDelightQuery query = KeyMetadata.FACTORY.selectFingerprintsForKeysOlderThan(new Date(timeUnit.toMillis(olderThan)));
|
||||
|
||||
List<byte[]> fingerprintList = new ArrayList<>();
|
||||
try (Cursor cursor = getReadableDb().query(query)) {
|
||||
while (cursor.moveToNext()) {
|
||||
byte[] fingerprint = KeyMetadata.FACTORY.selectFingerprintsForKeysOlderThanMapper().map(cursor);
|
||||
fingerprintList.add(fingerprint);
|
||||
}
|
||||
}
|
||||
return fingerprintList;
|
||||
}
|
||||
}
|
||||
@@ -1,274 +0,0 @@
|
||||
/*
|
||||
* 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.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.support.annotation.WorkerThread;
|
||||
|
||||
import com.squareup.sqldelight.SqlDelightQuery;
|
||||
import org.bouncycastle.bcpg.ArmoredOutputStream;
|
||||
import org.sufficientlysecure.keychain.model.Certification;
|
||||
import org.sufficientlysecure.keychain.model.KeyRingPublic;
|
||||
import org.sufficientlysecure.keychain.model.KeySignature;
|
||||
import org.sufficientlysecure.keychain.model.SubKey;
|
||||
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
|
||||
import org.sufficientlysecure.keychain.model.UserPacket;
|
||||
import org.sufficientlysecure.keychain.model.UserPacket.UserId;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing.VerificationStatus;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
||||
import timber.log.Timber;
|
||||
|
||||
|
||||
@WorkerThread
|
||||
public class KeyRepository extends AbstractDao {
|
||||
final ContentResolver contentResolver;
|
||||
final LocalPublicKeyStorage mLocalPublicKeyStorage;
|
||||
final LocalSecretKeyStorage localSecretKeyStorage;
|
||||
|
||||
OperationLog mLog;
|
||||
int mIndent;
|
||||
|
||||
public static KeyRepository create(Context context) {
|
||||
ContentResolver contentResolver = context.getContentResolver();
|
||||
LocalPublicKeyStorage localPublicKeyStorage = LocalPublicKeyStorage.getInstance(context);
|
||||
LocalSecretKeyStorage localSecretKeyStorage = LocalSecretKeyStorage.getInstance(context);
|
||||
KeychainDatabase database = KeychainDatabase.getInstance(context);
|
||||
DatabaseNotifyManager databaseNotifyManager = DatabaseNotifyManager.create(context);
|
||||
|
||||
return new KeyRepository(contentResolver, database, databaseNotifyManager, localPublicKeyStorage, localSecretKeyStorage);
|
||||
}
|
||||
|
||||
private KeyRepository(ContentResolver contentResolver, KeychainDatabase database,
|
||||
DatabaseNotifyManager databaseNotifyManager,
|
||||
LocalPublicKeyStorage localPublicKeyStorage,
|
||||
LocalSecretKeyStorage localSecretKeyStorage) {
|
||||
this(contentResolver, database, databaseNotifyManager, localPublicKeyStorage, localSecretKeyStorage, new OperationLog(), 0);
|
||||
}
|
||||
|
||||
KeyRepository(ContentResolver contentResolver, KeychainDatabase database,
|
||||
DatabaseNotifyManager databaseNotifyManager,
|
||||
LocalPublicKeyStorage localPublicKeyStorage,
|
||||
LocalSecretKeyStorage localSecretKeyStorage,
|
||||
OperationLog log, int indent) {
|
||||
super(database, databaseNotifyManager);
|
||||
this.contentResolver = contentResolver;
|
||||
mLocalPublicKeyStorage = localPublicKeyStorage;
|
||||
this.localSecretKeyStorage = localSecretKeyStorage;
|
||||
mIndent = indent;
|
||||
mLog = log;
|
||||
}
|
||||
|
||||
public OperationLog getLog() {
|
||||
return mLog;
|
||||
}
|
||||
|
||||
public void log(LogType type) {
|
||||
if (mLog != null) {
|
||||
mLog.add(type, mIndent);
|
||||
}
|
||||
}
|
||||
|
||||
public void log(LogType type, Object... parameters) {
|
||||
if (mLog != null) {
|
||||
mLog.add(type, mIndent, parameters);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearLog() {
|
||||
mLog = new OperationLog();
|
||||
}
|
||||
|
||||
public CanonicalizedPublicKeyRing getCanonicalizedPublicKeyRing(long masterKeyId) throws NotFoundException {
|
||||
UnifiedKeyInfo unifiedKeyInfo = getUnifiedKeyInfo(masterKeyId);
|
||||
if (unifiedKeyInfo == null) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
byte[] publicKeyData = loadPublicKeyRingData(masterKeyId);
|
||||
return new CanonicalizedPublicKeyRing(publicKeyData, unifiedKeyInfo.verified());
|
||||
}
|
||||
|
||||
public CanonicalizedSecretKeyRing getCanonicalizedSecretKeyRing(long masterKeyId) throws NotFoundException {
|
||||
UnifiedKeyInfo unifiedKeyInfo = getUnifiedKeyInfo(masterKeyId);
|
||||
if (unifiedKeyInfo == null || !unifiedKeyInfo.has_any_secret()) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
byte[] secretKeyData = loadSecretKeyRingData(masterKeyId);
|
||||
if (secretKeyData == null) {
|
||||
throw new IllegalStateException("Missing expected secret key data!");
|
||||
}
|
||||
return new CanonicalizedSecretKeyRing(secretKeyData, unifiedKeyInfo.verified());
|
||||
}
|
||||
|
||||
public List<Long> getAllMasterKeyIds() {
|
||||
SqlDelightQuery query = KeyRingPublic.FACTORY.selectAllMasterKeyIds();
|
||||
return mapAllRows(query, KeyRingPublic.FACTORY.selectAllMasterKeyIdsMapper()::map);
|
||||
}
|
||||
|
||||
public List<Long> getMasterKeyIdsBySigner(List<Long> signerMasterKeyIds) {
|
||||
long[] signerKeyIds = getLongListAsArray(signerMasterKeyIds);
|
||||
SqlDelightQuery query = KeySignature.FACTORY.selectMasterKeyIdsBySigner(signerKeyIds);
|
||||
return mapAllRows(query, KeySignature.FACTORY.selectMasterKeyIdsBySignerMapper()::map);
|
||||
}
|
||||
|
||||
public Long getMasterKeyIdBySubkeyId(long subKeyId) {
|
||||
SqlDelightQuery query = SubKey.FACTORY.selectMasterKeyIdBySubkey(subKeyId);
|
||||
return mapSingleRow(query, SubKey.FACTORY.selectMasterKeyIdBySubkeyMapper()::map);
|
||||
}
|
||||
|
||||
public UnifiedKeyInfo getUnifiedKeyInfo(long masterKeyId) {
|
||||
SqlDelightQuery query = SubKey.FACTORY.selectUnifiedKeyInfoByMasterKeyId(masterKeyId);
|
||||
return mapSingleRow(query, SubKey.UNIFIED_KEY_INFO_MAPPER::map);
|
||||
}
|
||||
|
||||
public List<UnifiedKeyInfo> getUnifiedKeyInfo(long... masterKeyIds) {
|
||||
SqlDelightQuery query = SubKey.FACTORY.selectUnifiedKeyInfoByMasterKeyIds(masterKeyIds);
|
||||
return mapAllRows(query, SubKey.UNIFIED_KEY_INFO_MAPPER::map);
|
||||
}
|
||||
|
||||
public List<UnifiedKeyInfo> getUnifiedKeyInfosByMailAddress(String mailAddress) {
|
||||
SqlDelightQuery query = SubKey.FACTORY.selectUnifiedKeyInfoSearchMailAddress('%' + mailAddress + '%');
|
||||
return mapAllRows(query, SubKey.UNIFIED_KEY_INFO_MAPPER::map);
|
||||
}
|
||||
|
||||
public List<UnifiedKeyInfo> getAllUnifiedKeyInfo() {
|
||||
SqlDelightQuery query = SubKey.FACTORY.selectAllUnifiedKeyInfo();
|
||||
return mapAllRows(query, SubKey.UNIFIED_KEY_INFO_MAPPER::map);
|
||||
}
|
||||
|
||||
public List<UnifiedKeyInfo> getAllUnifiedKeyInfoWithSecret() {
|
||||
SqlDelightQuery query = SubKey.FACTORY.selectAllUnifiedKeyInfoWithSecret();
|
||||
return mapAllRows(query, SubKey.UNIFIED_KEY_INFO_MAPPER::map);
|
||||
}
|
||||
|
||||
public List<UserId> getUserIds(long... masterKeyIds) {
|
||||
SqlDelightQuery query = UserPacket.FACTORY.selectUserIdsByMasterKeyId(masterKeyIds);
|
||||
return mapAllRows(query, UserPacket.USER_ID_MAPPER::map);
|
||||
}
|
||||
|
||||
public List<String> getConfirmedUserIds(long masterKeyId) {
|
||||
SqlDelightQuery query = UserPacket.FACTORY.selectUserIdsByMasterKeyIdAndVerification(
|
||||
Certification.FACTORY, masterKeyId, VerificationStatus.VERIFIED_SECRET);
|
||||
return mapAllRows(query, (cursor) -> UserPacket.USER_ID_MAPPER.map(cursor).user_id());
|
||||
}
|
||||
|
||||
public List<SubKey> getSubKeysByMasterKeyId(long masterKeyId) {
|
||||
SqlDelightQuery query = SubKey.FACTORY.selectSubkeysByMasterKeyId(masterKeyId);
|
||||
return mapAllRows(query, SubKey.SUBKEY_MAPPER::map);
|
||||
}
|
||||
|
||||
public SecretKeyType getSecretKeyType(long keyId) throws NotFoundException {
|
||||
SqlDelightQuery query = SubKey.FACTORY.selectSecretKeyType(keyId);
|
||||
return mapSingleRowOrThrow(query, SubKey.SKT_MAPPER::map);
|
||||
}
|
||||
|
||||
public byte[] getFingerprintByKeyId(long keyId) throws NotFoundException {
|
||||
SqlDelightQuery query = SubKey.FACTORY.selectFingerprintByKeyId(keyId);
|
||||
return mapSingleRowOrThrow(query, SubKey.FACTORY.selectFingerprintByKeyIdMapper()::map);
|
||||
}
|
||||
|
||||
private byte[] getKeyRingAsArmoredData(byte[] data) throws IOException {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
ArmoredOutputStream aos = new ArmoredOutputStream(bos);
|
||||
|
||||
aos.write(data);
|
||||
aos.close();
|
||||
|
||||
return bos.toByteArray();
|
||||
}
|
||||
|
||||
public String getPublicKeyRingAsArmoredString(long masterKeyId) throws NotFoundException, IOException {
|
||||
byte[] data = loadPublicKeyRingData(masterKeyId);
|
||||
byte[] armoredData = getKeyRingAsArmoredData(data);
|
||||
return new String(armoredData);
|
||||
}
|
||||
|
||||
public byte[] getSecretKeyRingAsArmoredData(long masterKeyId) throws NotFoundException, IOException {
|
||||
byte[] data = loadSecretKeyRingData(masterKeyId);
|
||||
return getKeyRingAsArmoredData(data);
|
||||
}
|
||||
|
||||
public ContentResolver getContentResolver() {
|
||||
return contentResolver;
|
||||
}
|
||||
|
||||
public final byte[] loadPublicKeyRingData(long masterKeyId) throws NotFoundException {
|
||||
SqlDelightQuery query = KeyRingPublic.FACTORY.selectByMasterKeyId(masterKeyId);
|
||||
try (Cursor cursor = getReadableDb().query(query)) {
|
||||
if (cursor.moveToFirst()) {
|
||||
KeyRingPublic keyRingPublic = KeyRingPublic.MAPPER.map(cursor);
|
||||
byte[] keyRingData = keyRingPublic.key_ring_data();
|
||||
if (keyRingData == null) {
|
||||
keyRingData = mLocalPublicKeyStorage.readPublicKey(masterKeyId);
|
||||
}
|
||||
return keyRingData;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Timber.e(e, "Error reading public key from storage!");
|
||||
}
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
public final byte[] loadSecretKeyRingData(long masterKeyId) throws NotFoundException {
|
||||
try {
|
||||
return localSecretKeyStorage.readSecretKey(masterKeyId);
|
||||
} catch (IOException e) {
|
||||
Timber.e(e, "Error reading secret key from storage!");
|
||||
throw new NotFoundException();
|
||||
}
|
||||
}
|
||||
|
||||
public long getSecretSignId(long masterKeyId) throws NotFoundException {
|
||||
SqlDelightQuery query = SubKey.FACTORY.selectEffectiveSignKeyIdByMasterKeyId(masterKeyId);
|
||||
return mapSingleRowOrThrow(query, SubKey.FACTORY.selectEffectiveSignKeyIdByMasterKeyIdMapper()::map);
|
||||
}
|
||||
|
||||
public long getSecretAuthenticationId(long masterKeyId) throws NotFoundException {
|
||||
SqlDelightQuery query = SubKey.FACTORY.selectEffectiveAuthKeyIdByMasterKeyId(masterKeyId);
|
||||
return mapSingleRowOrThrow(query, SubKey.FACTORY.selectEffectiveAuthKeyIdByMasterKeyIdMapper()::map);
|
||||
}
|
||||
|
||||
public static class NotFoundException extends Exception {
|
||||
public NotFoundException() {
|
||||
}
|
||||
|
||||
public NotFoundException(String name) {
|
||||
super(name);
|
||||
}
|
||||
}
|
||||
|
||||
private long[] getLongListAsArray(List<Long> longList) {
|
||||
long[] longs = new long[longList.size()];
|
||||
int i = 0;
|
||||
for (Long aLong : longList) {
|
||||
longs[i++] = aLong;
|
||||
}
|
||||
return longs;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -24,12 +24,12 @@ import org.sufficientlysecure.keychain.Constants;
|
||||
|
||||
public class KeychainContract {
|
||||
|
||||
interface KeyRingsColumns {
|
||||
public interface KeyRingsColumns {
|
||||
String MASTER_KEY_ID = "master_key_id"; // not a database id
|
||||
String KEY_RING_DATA = "key_ring_data"; // PGPPublicKeyRing / PGPSecretKeyRing blob
|
||||
}
|
||||
|
||||
interface KeysColumns {
|
||||
public interface KeysColumns {
|
||||
String MASTER_KEY_ID = "master_key_id"; // not a database id
|
||||
String RANK = "rank";
|
||||
|
||||
@@ -51,12 +51,12 @@ public class KeychainContract {
|
||||
String EXPIRY = "expiry";
|
||||
}
|
||||
|
||||
interface KeySignaturesColumns {
|
||||
public interface KeySignaturesColumns {
|
||||
String MASTER_KEY_ID = "master_key_id"; // not a database id
|
||||
String SIGNER_KEY_ID = "signer_key_id";
|
||||
}
|
||||
|
||||
interface UserPacketsColumns {
|
||||
public interface UserPacketsColumns {
|
||||
String MASTER_KEY_ID = "master_key_id"; // foreign key to key_rings._ID
|
||||
String TYPE = "type"; // not a database id
|
||||
String USER_ID = "user_id"; // not a database id
|
||||
@@ -69,7 +69,7 @@ public class KeychainContract {
|
||||
String IS_REVOKED = "is_revoked";
|
||||
}
|
||||
|
||||
interface CertsColumns {
|
||||
public interface CertsColumns {
|
||||
String MASTER_KEY_ID = "master_key_id";
|
||||
String RANK = "rank";
|
||||
String KEY_ID_CERTIFIER = "key_id_certifier";
|
||||
@@ -79,12 +79,12 @@ public class KeychainContract {
|
||||
String DATA = "data";
|
||||
}
|
||||
|
||||
interface ApiAppsAllowedKeysColumns {
|
||||
public interface ApiAppsAllowedKeysColumns {
|
||||
String KEY_ID = "key_id"; // not a database id
|
||||
String PACKAGE_NAME = "package_name"; // foreign key to api_apps.package_name
|
||||
}
|
||||
|
||||
interface OverriddenWarnings {
|
||||
public interface OverriddenWarnings {
|
||||
String IDENTIFIER = "identifier";
|
||||
}
|
||||
|
||||
|
||||
@@ -1,473 +0,0 @@
|
||||
/*
|
||||
* 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.File;
|
||||
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.SQLException;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
|
||||
import org.sufficientlysecure.keychain.ApiAllowedKeysModel;
|
||||
import org.sufficientlysecure.keychain.ApiAppsModel;
|
||||
import org.sufficientlysecure.keychain.AutocryptPeersModel;
|
||||
import org.sufficientlysecure.keychain.CertsModel;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.KeyMetadataModel;
|
||||
import org.sufficientlysecure.keychain.KeyRingsPublicModel;
|
||||
import org.sufficientlysecure.keychain.KeySignaturesModel;
|
||||
import org.sufficientlysecure.keychain.KeysModel;
|
||||
import org.sufficientlysecure.keychain.OverriddenWarningsModel;
|
||||
import org.sufficientlysecure.keychain.UserPacketsModel;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.CertsColumns;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPacketsColumns;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
import timber.log.Timber;
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public class KeychainDatabase {
|
||||
private static final String DATABASE_NAME = "openkeychain.db";
|
||||
private static final int DATABASE_VERSION = 29;
|
||||
private final SupportSQLiteOpenHelper supportSQLiteOpenHelper;
|
||||
|
||||
private static KeychainDatabase sInstance;
|
||||
|
||||
public static KeychainDatabase getInstance(Context context) {
|
||||
sInstance = new KeychainDatabase(context.getApplicationContext());
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static void resetSingleton() {
|
||||
sInstance = null;
|
||||
}
|
||||
|
||||
public interface Tables {
|
||||
String KEY_RINGS_PUBLIC = "keyrings_public";
|
||||
String KEYS = "keys";
|
||||
String KEY_SIGNATURES = "key_signatures";
|
||||
String USER_PACKETS = "user_packets";
|
||||
String CERTS = "certs";
|
||||
String API_ALLOWED_KEYS = "api_allowed_keys";
|
||||
String OVERRIDDEN_WARNINGS = "overridden_warnings";
|
||||
}
|
||||
|
||||
private KeychainDatabase(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, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
KeychainDatabase.this.onUpgrade(db, context, oldVersion, newVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
KeychainDatabase.this.onDowngrade();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(SupportSQLiteDatabase db) {
|
||||
super.onOpen(db);
|
||||
if (!db.isReadOnly()) {
|
||||
// Enable foreign key constraints
|
||||
db.execSQL("PRAGMA foreign_keys=ON;");
|
||||
}
|
||||
}
|
||||
}).build());
|
||||
}
|
||||
|
||||
public SupportSQLiteDatabase getReadableDatabase() {
|
||||
return supportSQLiteOpenHelper.getReadableDatabase();
|
||||
}
|
||||
|
||||
public SupportSQLiteDatabase getWritableDatabase() {
|
||||
return supportSQLiteOpenHelper.getWritableDatabase();
|
||||
}
|
||||
|
||||
private void onCreate(SupportSQLiteDatabase db, Context context) {
|
||||
Timber.w("Creating database...");
|
||||
|
||||
db.execSQL(KeyRingsPublicModel.CREATE_TABLE);
|
||||
db.execSQL(KeysModel.CREATE_TABLE);
|
||||
db.execSQL(UserPacketsModel.CREATE_TABLE);
|
||||
db.execSQL(CertsModel.CREATE_TABLE);
|
||||
db.execSQL(KeyMetadataModel.CREATE_TABLE);
|
||||
db.execSQL(KeySignaturesModel.CREATE_TABLE);
|
||||
db.execSQL(ApiAppsModel.CREATE_TABLE);
|
||||
db.execSQL(OverriddenWarningsModel.CREATE_TABLE);
|
||||
db.execSQL(AutocryptPeersModel.CREATE_TABLE);
|
||||
db.execSQL(ApiAllowedKeysModel.CREATE_TABLE);
|
||||
db.execSQL(KeysModel.UNIFIEDKEYVIEW);
|
||||
|
||||
db.execSQL("CREATE INDEX keys_by_rank ON keys (" + KeysColumns.RANK + ", " + KeysColumns.MASTER_KEY_ID + ");");
|
||||
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 + ");");
|
||||
db.execSQL("CREATE INDEX uids_by_email ON user_packets ("
|
||||
+ UserPacketsColumns.EMAIL + ");");
|
||||
|
||||
Preferences.getPreferences(context).setKeySignaturesTableInitialized();
|
||||
}
|
||||
|
||||
private void onUpgrade(SupportSQLiteDatabase db, Context context, int oldVersion, int newVersion) {
|
||||
Timber.d("Upgrading db from " + oldVersion + " to " + newVersion);
|
||||
|
||||
switch (oldVersion) {
|
||||
case 1:
|
||||
// add has_secret for all who are upgrading from a beta version
|
||||
try {
|
||||
db.execSQL("ALTER TABLE keys ADD COLUMN has_secret INTEGER");
|
||||
} catch (Exception e) {
|
||||
// never mind, the column probably already existed
|
||||
}
|
||||
// fall through
|
||||
case 2:
|
||||
// ECC support
|
||||
try {
|
||||
db.execSQL("ALTER TABLE keys ADD COLUMN key_curve_oid TEXT");
|
||||
} catch (Exception e) {
|
||||
// never mind, the column probably already existed
|
||||
}
|
||||
// fall through
|
||||
case 3:
|
||||
// better s2k detection, we need consolidate
|
||||
// fall through
|
||||
case 4:
|
||||
try {
|
||||
db.execSQL("ALTER TABLE keys ADD COLUMN can_authenticate INTEGER");
|
||||
} catch (Exception e) {
|
||||
// never mind, the column probably already existed
|
||||
}
|
||||
// fall through
|
||||
case 5:
|
||||
// do consolidate for 3.0 beta3
|
||||
// fall through
|
||||
case 6:
|
||||
db.execSQL("ALTER TABLE user_ids ADD COLUMN type INTEGER");
|
||||
db.execSQL("ALTER TABLE user_ids ADD COLUMN attribute_data BLOB");
|
||||
case 7:
|
||||
// new table for allowed key ids in API
|
||||
try {
|
||||
db.execSQL(ApiAppsModel.CREATE_TABLE);
|
||||
} catch (Exception e) {
|
||||
// never mind, the column probably already existed
|
||||
}
|
||||
case 8:
|
||||
// tbale name for user_ids changed to user_packets
|
||||
db.execSQL("DROP TABLE IF EXISTS certs");
|
||||
db.execSQL("DROP TABLE IF EXISTS user_ids");
|
||||
db.execSQL("CREATE TABLE IF NOT EXISTS user_packets("
|
||||
+ "master_key_id INTEGER, "
|
||||
+ "type INT, "
|
||||
+ "user_id TEXT, "
|
||||
+ "attribute_data BLOB, "
|
||||
|
||||
+ "is_primary INTEGER, "
|
||||
+ "is_revoked INTEGER, "
|
||||
+ "rank INTEGER, "
|
||||
|
||||
+ "PRIMARY KEY(master_key_id, rank), "
|
||||
+ "FOREIGN KEY(master_key_id) REFERENCES "
|
||||
+ "keyrings_public(master_key_id) ON DELETE CASCADE"
|
||||
+ ")");
|
||||
db.execSQL("CREATE TABLE IF NOT EXISTS certs("
|
||||
+ "master_key_id INTEGER,"
|
||||
+ "rank INTEGER, " // rank of certified uid
|
||||
|
||||
+ "key_id_certifier INTEGER, " // certifying key
|
||||
+ "type INTEGER, "
|
||||
+ "verified INTEGER, "
|
||||
+ "creation INTEGER, "
|
||||
|
||||
+ "data BLOB, "
|
||||
|
||||
+ "PRIMARY KEY(master_key_id, rank, "
|
||||
+ "key_id_certifier), "
|
||||
+ "FOREIGN KEY(master_key_id) REFERENCES "
|
||||
+ "keyrings_public(master_key_id) ON DELETE CASCADE,"
|
||||
+ "FOREIGN KEY(master_key_id, rank) REFERENCES "
|
||||
+ "user_packets(master_key_id, rank) ON DELETE CASCADE"
|
||||
+ ")");
|
||||
case 9:
|
||||
// do nothing here, just consolidate
|
||||
case 10:
|
||||
// fix problems in database, see #1402 for details
|
||||
// https://github.com/open-keychain/open-keychain/issues/1402
|
||||
// no longer needed, api_accounts is deprecated
|
||||
// db.execSQL("DELETE FROM api_accounts WHERE key_id BETWEEN 0 AND 3");
|
||||
case 11:
|
||||
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"
|
||||
+ ")");
|
||||
case 12:
|
||||
// do nothing here, just consolidate
|
||||
case 13:
|
||||
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 + ");");
|
||||
case 14:
|
||||
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");
|
||||
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)");
|
||||
case 16:
|
||||
// splitUserId changed: Execute consolidate for new parsing of name, email
|
||||
case 17:
|
||||
// splitUserId changed: Execute consolidate for new parsing of name, email
|
||||
case 18:
|
||||
db.execSQL("ALTER TABLE keys ADD COLUMN is_secure INTEGER");
|
||||
case 19:
|
||||
// emergency fix for crashing consolidate
|
||||
db.execSQL("UPDATE keys SET is_secure = 1;");
|
||||
case 20:
|
||||
db.execSQL(
|
||||
"CREATE TABLE IF NOT EXISTS overridden_warnings ("
|
||||
+ "_id INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
+ "identifier TEXT NOT NULL UNIQUE "
|
||||
+ ")");
|
||||
|
||||
case 21:
|
||||
try {
|
||||
db.execSQL("ALTER TABLE updated_keys ADD COLUMN seen_on_keyservers INTEGER;");
|
||||
} catch (SQLiteException e) {
|
||||
// don't bother, the column probably already existed
|
||||
}
|
||||
|
||||
case 22:
|
||||
db.execSQL("CREATE TABLE IF NOT EXISTS api_autocrypt_peers ("
|
||||
+ "package_name TEXT NOT NULL, "
|
||||
+ "identifier TEXT NOT NULL, "
|
||||
+ "last_updated INTEGER NOT NULL, "
|
||||
+ "last_seen_key INTEGER NOT NULL, "
|
||||
+ "state INTEGER NOT NULL, "
|
||||
+ "master_key_id INTEGER, "
|
||||
+ "PRIMARY KEY(package_name, identifier), "
|
||||
+ "FOREIGN KEY(package_name) REFERENCES api_apps(package_name) ON DELETE CASCADE"
|
||||
+ ")");
|
||||
|
||||
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"
|
||||
+ ")");
|
||||
|
||||
case 24: {
|
||||
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 " +
|
||||
"FROM tmp WHERE state = 3"); // Autocrypt 0.X, state = "available"
|
||||
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 " +
|
||||
"FROM tmp WHERE state = 4"); // from Autocrypt 0.X, state = "mutual"
|
||||
db.execSQL("DROP TABLE tmp");
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
db.execSQL("CREATE INDEX IF NOT EXISTS uids_by_email ON user_packets (email);");
|
||||
db.execSQL("DROP INDEX keys_by_rank");
|
||||
db.execSQL("CREATE INDEX keys_by_rank ON keys(rank, master_key_id);");
|
||||
}
|
||||
|
||||
case 25: {
|
||||
try {
|
||||
migrateSecretKeysFromDbToLocalStorage(db, context);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Error migrating secret keys! This is bad!!");
|
||||
}
|
||||
}
|
||||
|
||||
case 26:
|
||||
migrateUpdatedKeysToKeyMetadataTable(db);
|
||||
|
||||
case 27:
|
||||
renameApiAutocryptPeersTable(db);
|
||||
|
||||
case 28:
|
||||
recreateUnifiedKeyView(db);
|
||||
// drop old table from version 20
|
||||
db.execSQL("DROP TABLE IF EXISTS api_accounts");
|
||||
}
|
||||
}
|
||||
|
||||
private void recreateUnifiedKeyView(SupportSQLiteDatabase db) {
|
||||
// noinspection deprecation
|
||||
db.execSQL("DROP VIEW IF EXISTS " + KeysModel.UNIFIEDKEYVIEW_VIEW_NAME);
|
||||
db.execSQL(KeysModel.UNIFIEDKEYVIEW);
|
||||
}
|
||||
|
||||
private void migrateSecretKeysFromDbToLocalStorage(SupportSQLiteDatabase db, Context context) 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);
|
||||
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");
|
||||
}
|
||||
|
||||
private void migrateUpdatedKeysToKeyMetadataTable(SupportSQLiteDatabase db) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private void renameApiAutocryptPeersTable(SupportSQLiteDatabase db) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private void onDowngrade() {
|
||||
// Downgrade is ok for the debug version, makes it easier to work with branches
|
||||
if (Constants.DEBUG) {
|
||||
return;
|
||||
}
|
||||
// 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!");
|
||||
}
|
||||
|
||||
private static void copy(File in, File out) throws IOException {
|
||||
FileInputStream is = new FileInputStream(in);
|
||||
FileOutputStream os = new FileOutputStream(out);
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
public static void debugBackup(Context context, boolean restore) throws IOException {
|
||||
if (!Constants.DEBUG) {
|
||||
return;
|
||||
}
|
||||
|
||||
File in;
|
||||
File out;
|
||||
if (restore) {
|
||||
in = context.getDatabasePath("debug_backup.db");
|
||||
out = context.getDatabasePath(DATABASE_NAME);
|
||||
} else {
|
||||
in = context.getDatabasePath(DATABASE_NAME);
|
||||
out = context.getDatabasePath("debug_backup.db");
|
||||
// noinspection ResultOfMethodCallIgnored - this is a pure debug feature, anyways
|
||||
out.createNewFile();
|
||||
}
|
||||
if (!in.canRead()) {
|
||||
throw new IOException("Cannot read " + in.getName());
|
||||
}
|
||||
if (!out.canWrite()) {
|
||||
throw new IOException("Cannot write " + out.getName());
|
||||
}
|
||||
copy(in, out);
|
||||
}
|
||||
|
||||
// DANGEROUS, use in test code ONLY!
|
||||
public void clearDatabase() {
|
||||
getWritableDatabase().execSQL("delete from " + KeyRingsPublicModel.TABLE_NAME);
|
||||
getWritableDatabase().execSQL("delete from " + ApiAllowedKeysModel.TABLE_NAME);
|
||||
getWritableDatabase().execSQL("delete from api_apps");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -29,17 +29,18 @@ import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.sufficientlysecure.keychain.KeychainDatabase;
|
||||
import org.sufficientlysecure.keychain.daos.DatabaseNotifyManager;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeySignatures;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPacketsColumns;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
|
||||
import org.sufficientlysecure.keychain.KeychainDatabase.Tables;
|
||||
import timber.log.Timber;
|
||||
|
||||
|
||||
public class KeychainProvider extends ContentProvider implements SimpleContentResolverInterface {
|
||||
public class KeychainProvider extends ContentProvider {
|
||||
private static final int KEY_RING_KEYS = 201;
|
||||
private static final int KEY_RING_USER_IDS = 202;
|
||||
private static final int KEY_RING_PUBLIC = 203;
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
/*
|
||||
* 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 LocalPublicKeyStorage {
|
||||
private static final String FORMAT_STR_PUBLIC_KEY = "0x%016x.pub";
|
||||
private static final String PUBLIC_KEYS_DIR_NAME = "public_keys";
|
||||
|
||||
|
||||
private final File localPublicKeysDir;
|
||||
|
||||
|
||||
public static LocalPublicKeyStorage getInstance(Context context) {
|
||||
File localPublicKeysDir = new File(context.getFilesDir(), PUBLIC_KEYS_DIR_NAME);
|
||||
return new LocalPublicKeyStorage(localPublicKeysDir);
|
||||
}
|
||||
|
||||
private LocalPublicKeyStorage(File localPublicKeysDir) {
|
||||
this.localPublicKeysDir = localPublicKeysDir;
|
||||
}
|
||||
|
||||
private File getPublicKeyFile(long masterKeyId) throws IOException {
|
||||
if (!localPublicKeysDir.exists()) {
|
||||
localPublicKeysDir.mkdir();
|
||||
}
|
||||
if (!localPublicKeysDir.isDirectory()) {
|
||||
throw new IOException("Failed creating public key directory!");
|
||||
}
|
||||
|
||||
String keyFilename = String.format(FORMAT_STR_PUBLIC_KEY, masterKeyId);
|
||||
return new File(localPublicKeysDir, keyFilename);
|
||||
}
|
||||
|
||||
void writePublicKey(long masterKeyId, byte[] encoded) throws IOException {
|
||||
File publicKeyFile = getPublicKeyFile(masterKeyId);
|
||||
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(publicKeyFile);
|
||||
try {
|
||||
fileOutputStream.write(encoded);
|
||||
} finally {
|
||||
Util.closeQuietly(fileOutputStream);
|
||||
}
|
||||
}
|
||||
|
||||
byte[] readPublicKey(long masterKeyId) throws IOException {
|
||||
File publicKeyFile = getPublicKeyFile(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 deletePublicKey(long masterKeyId) throws IOException {
|
||||
File publicKeyFile = getPublicKeyFile(masterKeyId);
|
||||
if (publicKeyFile.exists()) {
|
||||
boolean deleteSuccess = publicKeyFile.delete();
|
||||
if (!deleteSuccess) {
|
||||
throw new IOException("File exists, but could not be deleted!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
/*
|
||||
* 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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* 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 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;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.OverriddenWarnings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
|
||||
|
||||
|
||||
public class OverriddenWarningsRepository {
|
||||
private final Context context;
|
||||
private KeychainDatabase keychainDatabase;
|
||||
|
||||
public static OverriddenWarningsRepository createOverriddenWarningsRepository(Context context) {
|
||||
return new OverriddenWarningsRepository(context);
|
||||
}
|
||||
|
||||
private OverriddenWarningsRepository(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
private KeychainDatabase getDb() {
|
||||
if (keychainDatabase == null) {
|
||||
keychainDatabase = KeychainDatabase.getInstance(context);
|
||||
}
|
||||
return keychainDatabase;
|
||||
}
|
||||
|
||||
public boolean isWarningOverridden(String identifier) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
public void putOverride(String identifier) {
|
||||
SupportSQLiteDatabase db = getDb().getWritableDatabase();
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(OverriddenWarnings.IDENTIFIER, identifier);
|
||||
db.insert(Tables.OVERRIDDEN_WARNINGS, SQLiteDatabase.CONFLICT_REPLACE, cv);
|
||||
}
|
||||
|
||||
public void deleteOverride(String identifier) {
|
||||
SupportSQLiteDatabase db = getDb().getWritableDatabase();
|
||||
db.delete(Tables.OVERRIDDEN_WARNINGS, OverriddenWarnings.IDENTIFIER + " = ?", new String[] { identifier });
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* 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 android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
|
||||
/** This interface contains the principal methods for database access
|
||||
* from {#android.content.ContentResolver}. It is used to allow substitution
|
||||
* of a ContentResolver in DAOs.
|
||||
*
|
||||
* @see ApiAppDao
|
||||
*/
|
||||
public interface SimpleContentResolverInterface {
|
||||
Cursor query(Uri contentUri, String[] projection, String selection, String[] selectionArgs, String sortOrder);
|
||||
|
||||
Uri insert(Uri contentUri, ContentValues values);
|
||||
|
||||
int update(Uri contentUri, ContentValues values, String where, String[] selectionArgs);
|
||||
|
||||
int delete(Uri contentUri, String where, String[] selectionArgs);
|
||||
}
|
||||
Reference in New Issue
Block a user