open-keychain/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java
2018-07-02 14:50:11 +02:00

324 lines
12 KiB
Java

/*
* 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.ArrayList;
import java.util.HashMap;
import android.arch.persistence.db.SupportSQLiteDatabase;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import com.squareup.sqldelight.SqlDelightQuery;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.sufficientlysecure.keychain.model.KeyRingPublic;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
import timber.log.Timber;
public class KeyRepository {
// If we ever switch to api level 11, we can ditch this whole mess!
public static final int FIELD_TYPE_NULL = 1;
// this is called integer to stay coherent with the constants in Cursor (api level 11)
public static final int FIELD_TYPE_INTEGER = 2;
public static final int FIELD_TYPE_FLOAT = 3;
public static final int FIELD_TYPE_STRING = 4;
public static final int FIELD_TYPE_BLOB = 5;
final ContentResolver contentResolver;
final LocalPublicKeyStorage mLocalPublicKeyStorage;
final LocalSecretKeyStorage localSecretKeyStorage;
final SupportSQLiteDatabase db;
OperationLog mLog;
int mIndent;
public static KeyRepository create(Context context) {
ContentResolver contentResolver = context.getContentResolver();
LocalPublicKeyStorage localPublicKeyStorage = LocalPublicKeyStorage.getInstance(context);
LocalSecretKeyStorage localSecretKeyStorage = LocalSecretKeyStorage.getInstance(context);
SupportSQLiteDatabase db = new KeychainDatabase(context).getWritableDatabase();
return new KeyRepository(contentResolver, db, localPublicKeyStorage, localSecretKeyStorage);
}
private KeyRepository(ContentResolver contentResolver, SupportSQLiteDatabase db,
LocalPublicKeyStorage localPublicKeyStorage,
LocalSecretKeyStorage localSecretKeyStorage) {
this(contentResolver, db, localPublicKeyStorage, localSecretKeyStorage, new OperationLog(), 0);
}
KeyRepository(ContentResolver contentResolver, SupportSQLiteDatabase db,
LocalPublicKeyStorage localPublicKeyStorage,
LocalSecretKeyStorage localSecretKeyStorage,
OperationLog log, int indent) {
this.contentResolver = contentResolver;
this.db = db;
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();
}
Object getGenericData(Uri uri, String column, int type) throws NotFoundException {
Object result = getGenericData(uri, new String[]{column}, new int[]{type}, null).get(column);
if (result == null) {
throw new NotFoundException();
}
return result;
}
Object getGenericData(Uri uri, String column, int type, String selection)
throws NotFoundException {
return getGenericData(uri, new String[]{column}, new int[]{type}, selection).get(column);
}
private HashMap<String, Object> getGenericData(Uri uri, String[] proj, int[] types)
throws NotFoundException {
return getGenericData(uri, proj, types, null);
}
private HashMap<String, Object> getGenericData(Uri uri, String[] proj, int[] types, String selection)
throws NotFoundException {
Cursor cursor = contentResolver.query(uri, proj, selection, null, null);
try {
HashMap<String, Object> result = new HashMap<>(proj.length);
if (cursor != null && cursor.moveToFirst()) {
int pos = 0;
for (String p : proj) {
switch (types[pos]) {
case FIELD_TYPE_NULL:
result.put(p, cursor.isNull(pos));
break;
case FIELD_TYPE_INTEGER:
result.put(p, cursor.getLong(pos));
break;
case FIELD_TYPE_FLOAT:
result.put(p, cursor.getFloat(pos));
break;
case FIELD_TYPE_STRING:
result.put(p, cursor.getString(pos));
break;
case FIELD_TYPE_BLOB:
result.put(p, cursor.getBlob(pos));
break;
}
pos += 1;
}
} else {
// If no data was found, throw an appropriate exception
throw new NotFoundException();
}
return result;
} finally {
if (cursor != null) {
cursor.close();
}
}
}
public HashMap<String, Object> getUnifiedData(long masterKeyId, String[] proj, int[] types)
throws NotFoundException {
return getGenericData(KeyRings.buildUnifiedKeyRingUri(masterKeyId), proj, types);
}
public long getMasterKeyId(long subKeyId) throws NotFoundException {
return (Long) getGenericData(KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId),
KeyRings.MASTER_KEY_ID, FIELD_TYPE_INTEGER);
}
public CachedPublicKeyRing getCachedPublicKeyRing(Uri queryUri) throws PgpKeyNotFoundException {
long masterKeyId = new CachedPublicKeyRing(this, queryUri).extractOrGetMasterKeyId();
return getCachedPublicKeyRing(masterKeyId);
}
public CachedPublicKeyRing getCachedPublicKeyRing(long id) {
return new CachedPublicKeyRing(this, KeyRings.buildUnifiedKeyRingUri(id));
}
public CanonicalizedPublicKeyRing getCanonicalizedPublicKeyRing(long id) throws NotFoundException {
return getCanonicalizedPublicKeyRing(KeyRings.buildUnifiedKeyRingUri(id));
}
public CanonicalizedPublicKeyRing getCanonicalizedPublicKeyRing(Uri queryUri) throws NotFoundException {
Cursor cursor = contentResolver.query(queryUri,
new String[] { KeyRings.MASTER_KEY_ID, KeyRings.VERIFIED }, null, null, null);
try {
if (cursor != null && cursor.moveToFirst()) {
long masterKeyId = cursor.getLong(0);
int verified = cursor.getInt(1);
byte[] publicKeyData = loadPublicKeyRingData(masterKeyId);
return new CanonicalizedPublicKeyRing(publicKeyData, verified);
} else {
throw new NotFoundException("Key not found!");
}
} finally {
if (cursor != null) {
cursor.close();
}
}
}
public CanonicalizedSecretKeyRing getCanonicalizedSecretKeyRing(long id) throws NotFoundException {
return getCanonicalizedSecretKeyRing(KeyRings.buildUnifiedKeyRingUri(id));
}
public CanonicalizedSecretKeyRing getCanonicalizedSecretKeyRing(Uri queryUri) throws NotFoundException {
Cursor cursor = contentResolver.query(queryUri,
new String[] { KeyRings.MASTER_KEY_ID, KeyRings.VERIFIED, KeyRings.HAS_ANY_SECRET }, null, null, null);
try {
if (cursor != null && cursor.moveToFirst()) {
long masterKeyId = cursor.getLong(0);
int verified = cursor.getInt(1);
int hasAnySecret = cursor.getInt(2);
if (hasAnySecret == 0) {
throw new NotFoundException("No secret key available or unknown public key!");
}
byte[] secretKeyData = loadSecretKeyRingData(masterKeyId);
return new CanonicalizedSecretKeyRing(secretKeyData, verified);
} else {
throw new NotFoundException("Key not found!");
}
} finally {
if (cursor != null) {
cursor.close();
}
}
}
public ArrayList<String> getConfirmedUserIds(long masterKeyId) throws NotFoundException {
Cursor cursor = contentResolver.query(UserPackets.buildUserIdsUri(masterKeyId),
new String[]{UserPackets.USER_ID}, UserPackets.VERIFIED + " = " + Certs.VERIFIED_SECRET, null, null
);
if (cursor == null) {
throw new NotFoundException("Key id for requested user ids not found");
}
try {
ArrayList<String> userIds = new ArrayList<>(cursor.getCount());
while (cursor.moveToNext()) {
String userId = cursor.getString(0);
userIds.add(userId);
}
return userIds;
} finally {
cursor.close();
}
}
private byte[] getKeyRingAsArmoredData(byte[] data) throws IOException, PgpGeneralException {
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, PgpGeneralException {
byte[] data = loadPublicKeyRingData(masterKeyId);
byte[] armoredData = getKeyRingAsArmoredData(data);
return new String(armoredData);
}
public byte[] getSecretKeyRingAsArmoredData(long masterKeyId)
throws NotFoundException, IOException, PgpGeneralException {
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 = db.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 static class NotFoundException extends Exception {
public NotFoundException() {
}
public NotFoundException(String name) {
super(name);
}
}
}