package org.sufficientlysecure.keychain.provider; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.util.Log; import org.sufficientlysecure.keychain.Constants; 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.UncachedKeyRing; 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.KeyRingData; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; 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 mContentResolver; final LocalPublicKeyStorage mLocalPublicKeyStorage; OperationLog mLog; int mIndent; public static KeyRepository createDatabaseInteractor(Context context) { ContentResolver contentResolver = context.getContentResolver(); LocalPublicKeyStorage localPublicKeyStorage = LocalPublicKeyStorage.getInstance(context); return new KeyRepository(contentResolver, localPublicKeyStorage); } private KeyRepository(ContentResolver contentResolver, LocalPublicKeyStorage localPublicKeyStorage) { this(contentResolver, localPublicKeyStorage, new OperationLog(), 0); } KeyRepository(ContentResolver contentResolver, LocalPublicKeyStorage localPublicKeyStorage, OperationLog log, int indent) { mContentResolver = contentResolver; mLocalPublicKeyStorage = localPublicKeyStorage; 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 getGenericDataOrNull(Uri uri, String column, int type) throws NotFoundException { return getGenericData(uri, new String[]{column}, new int[]{type}, null).get(column); } 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 = mContentResolver.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 = mContentResolver.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 = mContentResolver.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 = mContentResolver.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 String getKeyRingAsArmoredString(byte[] data) throws IOException, PgpGeneralException { UncachedKeyRing keyRing = UncachedKeyRing.decodeFromData(data); ByteArrayOutputStream bos = new ByteArrayOutputStream(); keyRing.encodeArmored(bos, null); return bos.toString("UTF-8"); } public String getPublicKeyRingAsArmoredString(long masterKeyId) throws NotFoundException, IOException, PgpGeneralException { byte[] data = loadPublicKeyRingData(masterKeyId); return getKeyRingAsArmoredString(data); } public ContentResolver getContentResolver() { return mContentResolver; } public final byte[] loadPublicKeyRingData(long masterKeyId) throws NotFoundException { byte[] data = (byte[]) getGenericDataOrNull(KeyRingData.buildPublicKeyRingUri(masterKeyId), KeyRingData.KEY_RING_DATA, FIELD_TYPE_BLOB); if (data == null) { try { data = mLocalPublicKeyStorage.readPublicKey(masterKeyId); } catch (IOException e) { Log.e(Constants.TAG, "Error reading public key from storage!", e); throw new NotFoundException(); } } if (data == null) { throw new NotFoundException(); } return data; } public final byte[] loadSecretKeyRingData(long masterKeyId) throws NotFoundException { byte[] data = (byte[]) getGenericDataOrNull(KeychainContract.KeyRingData.buildSecretKeyRingUri(masterKeyId), KeyRingData.KEY_RING_DATA, FIELD_TYPE_BLOB); if (data == null) { throw new NotFoundException(); } return data; } public static class NotFoundException extends Exception { public NotFoundException() { } public NotFoundException(String name) { super(name); } } }