clean up package structure

This commit is contained in:
Vincent Breitmoser
2018-07-02 15:07:11 +02:00
parent 8ee65f9b04
commit eb34f900e4
103 changed files with 189 additions and 249 deletions

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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";
}

View File

@@ -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");
}
}

View File

@@ -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;

View File

@@ -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!");
}
}
}
}

View File

@@ -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!");
}
}
}
}

View File

@@ -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 });
}
}

View File

@@ -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);
}