add LocalKeyStorage, don't store public keys larger than 50kb in database
This commit is contained in:
@@ -25,7 +25,6 @@ import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
||||
import org.sufficientlysecure.keychain.provider.DatabaseInteractor.NotFoundException;
|
||||
@@ -240,7 +239,7 @@ public class CachedPublicKeyRing extends KeyRing {
|
||||
|
||||
public byte[] getEncoded() throws PgpKeyNotFoundException {
|
||||
try {
|
||||
return mDatabaseInteractor.getPublicKeyRingData(getMasterKeyId());
|
||||
return mDatabaseInteractor.loadPublicKeyRingData(getMasterKeyId());
|
||||
} catch(DatabaseReadWriteInteractor.NotFoundException e) {
|
||||
throw new PgpKeyNotFoundException(e);
|
||||
}
|
||||
|
||||
@@ -7,9 +7,12 @@ 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;
|
||||
@@ -33,15 +36,25 @@ public class DatabaseInteractor {
|
||||
public static final int FIELD_TYPE_BLOB = 5;
|
||||
|
||||
final ContentResolver mContentResolver;
|
||||
final LocalPublicKeyStorage mLocalPublicKeyStorage;
|
||||
OperationLog mLog;
|
||||
int mIndent;
|
||||
|
||||
public DatabaseInteractor(ContentResolver contentResolver) {
|
||||
this(contentResolver, new OperationLog(), 0);
|
||||
public static DatabaseInteractor createDatabaseInteractor(Context context) {
|
||||
ContentResolver contentResolver = context.getContentResolver();
|
||||
LocalPublicKeyStorage localPublicKeyStorage = LocalPublicKeyStorage.getInstance(context);
|
||||
|
||||
return new DatabaseInteractor(contentResolver, localPublicKeyStorage);
|
||||
}
|
||||
|
||||
public DatabaseInteractor(ContentResolver contentResolver, OperationLog log, int indent) {
|
||||
private DatabaseInteractor(ContentResolver contentResolver, LocalPublicKeyStorage localPublicKeyStorage) {
|
||||
this(contentResolver, localPublicKeyStorage, new OperationLog(), 0);
|
||||
}
|
||||
|
||||
DatabaseInteractor(ContentResolver contentResolver, LocalPublicKeyStorage localPublicKeyStorage,
|
||||
OperationLog log, int indent) {
|
||||
mContentResolver = contentResolver;
|
||||
mLocalPublicKeyStorage = localPublicKeyStorage;
|
||||
mIndent = indent;
|
||||
mLog = log;
|
||||
}
|
||||
@@ -74,6 +87,10 @@ public class DatabaseInteractor {
|
||||
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);
|
||||
@@ -156,7 +173,7 @@ public class DatabaseInteractor {
|
||||
long masterKeyId = cursor.getLong(0);
|
||||
int verified = cursor.getInt(1);
|
||||
|
||||
byte[] publicKeyData = getPublicKeyRingData(masterKeyId);
|
||||
byte[] publicKeyData = loadPublicKeyRingData(masterKeyId);
|
||||
return new CanonicalizedPublicKeyRing(publicKeyData, verified);
|
||||
} else {
|
||||
throw new NotFoundException("Key not found!");
|
||||
@@ -184,7 +201,7 @@ public class DatabaseInteractor {
|
||||
throw new NotFoundException("No secret key available or unknown public key!");
|
||||
}
|
||||
|
||||
byte[] secretKeyData = getSecretKeyRingData(masterKeyId);
|
||||
byte[] secretKeyData = loadSecretKeyRingData(masterKeyId);
|
||||
return new CanonicalizedSecretKeyRing(secretKeyData, verified);
|
||||
} else {
|
||||
throw new NotFoundException("Key not found!");
|
||||
@@ -228,7 +245,7 @@ public class DatabaseInteractor {
|
||||
|
||||
public String getPublicKeyRingAsArmoredString(long masterKeyId)
|
||||
throws NotFoundException, IOException, PgpGeneralException {
|
||||
byte[] data = getPublicKeyRingData(masterKeyId);
|
||||
byte[] data = loadPublicKeyRingData(masterKeyId);
|
||||
return getKeyRingAsArmoredString(data);
|
||||
}
|
||||
|
||||
@@ -236,14 +253,35 @@ public class DatabaseInteractor {
|
||||
return mContentResolver;
|
||||
}
|
||||
|
||||
public byte[] getPublicKeyRingData(long masterKeyId) throws NotFoundException {
|
||||
return (byte[]) getGenericData(KeychainContract.KeyRingData.buildPublicKeyRingUri(masterKeyId),
|
||||
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 byte[] getSecretKeyRingData(long masterKeyId) throws NotFoundException {
|
||||
return (byte[]) getGenericData(KeychainContract.KeyRingData.buildSecretKeyRingUri(masterKeyId),
|
||||
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 {
|
||||
|
||||
@@ -19,6 +19,15 @@
|
||||
package org.sufficientlysecure.keychain.provider;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import android.content.ContentProviderOperation;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
@@ -68,15 +77,6 @@ import org.sufficientlysecure.keychain.util.ProgressFixedScaler;
|
||||
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||
import org.sufficientlysecure.keychain.util.Utf8Util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* This class contains high level methods for database access. Despite its
|
||||
* name, it is not only a helper but actually the main interface for all
|
||||
@@ -88,18 +88,26 @@ import java.util.concurrent.TimeUnit;
|
||||
* method is called to start a new one specifically.
|
||||
*/
|
||||
public class DatabaseReadWriteInteractor extends DatabaseInteractor {
|
||||
private static final int MAX_CACHED_KEY_SIZE = 1024 * 50;
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
public DatabaseReadWriteInteractor(Context context) {
|
||||
this(context, new OperationLog(), 0);
|
||||
public static DatabaseReadWriteInteractor createDatabaseReadWriteInteractor(Context context) {
|
||||
LocalPublicKeyStorage localPublicKeyStorage = LocalPublicKeyStorage.getInstance(context);
|
||||
|
||||
return new DatabaseReadWriteInteractor(context, localPublicKeyStorage);
|
||||
}
|
||||
|
||||
public DatabaseReadWriteInteractor(Context context, OperationLog log) {
|
||||
this(context, log, 0);
|
||||
private DatabaseReadWriteInteractor(Context context, LocalPublicKeyStorage localPublicKeyStorage) {
|
||||
this(context, localPublicKeyStorage, new OperationLog(), 0);
|
||||
}
|
||||
|
||||
public DatabaseReadWriteInteractor(Context context, OperationLog log, int indent) {
|
||||
super(context.getContentResolver(), log, indent);
|
||||
private DatabaseReadWriteInteractor(Context context, LocalPublicKeyStorage localPublicKeyStorage, OperationLog log) {
|
||||
this(context, localPublicKeyStorage, log, 0);
|
||||
}
|
||||
|
||||
private DatabaseReadWriteInteractor(Context context, LocalPublicKeyStorage localPublicKeyStorage, OperationLog log, int indent) {
|
||||
super(context.getContentResolver(), localPublicKeyStorage, log, indent);
|
||||
|
||||
mContext = context;
|
||||
}
|
||||
@@ -122,7 +130,7 @@ public class DatabaseReadWriteInteractor extends DatabaseInteractor {
|
||||
try {
|
||||
long masterKeyId = cursor.getLong(0);
|
||||
int verified = cursor.getInt(2);
|
||||
byte[] blob = getPublicKeyRingData(masterKeyId);
|
||||
byte[] blob = loadPublicKeyRingData(masterKeyId);
|
||||
if (blob != null) {
|
||||
result.put(masterKeyId, new CanonicalizedPublicKeyRing(blob, verified).getPublicKey());
|
||||
}
|
||||
@@ -191,18 +199,11 @@ public class DatabaseReadWriteInteractor extends DatabaseInteractor {
|
||||
operations = new ArrayList<>();
|
||||
|
||||
log(LogType.MSG_IP_INSERT_KEYRING);
|
||||
{ // insert keyring
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(KeyRingData.MASTER_KEY_ID, masterKeyId);
|
||||
try {
|
||||
values.put(KeyRingData.KEY_RING_DATA, keyRing.getEncoded());
|
||||
} catch (IOException e) {
|
||||
log(LogType.MSG_IP_ENCODE_FAIL);
|
||||
return SaveKeyringResult.RESULT_ERROR;
|
||||
}
|
||||
|
||||
Uri uri = KeyRingData.buildPublicKeyRingUri(masterKeyId);
|
||||
operations.add(ContentProviderOperation.newInsert(uri).withValues(values).build());
|
||||
try {
|
||||
writePublicKeyRing(keyRing, masterKeyId, operations);
|
||||
} catch (IOException e) {
|
||||
log(LogType.MSG_IP_ENCODE_FAIL);
|
||||
return SaveKeyringResult.RESULT_ERROR;
|
||||
}
|
||||
|
||||
log(LogType.MSG_IP_INSERT_SUBKEYS);
|
||||
@@ -582,6 +583,35 @@ public class DatabaseReadWriteInteractor extends DatabaseInteractor {
|
||||
|
||||
}
|
||||
|
||||
private void writePublicKeyRing(CanonicalizedPublicKeyRing keyRing, long masterKeyId,
|
||||
ArrayList<ContentProviderOperation> operations) throws IOException {
|
||||
byte[] encodedKey = keyRing.getEncoded();
|
||||
mLocalPublicKeyStorage.writePublicKey(masterKeyId, encodedKey);
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(KeyRingData.MASTER_KEY_ID, masterKeyId);
|
||||
if (encodedKey.length < MAX_CACHED_KEY_SIZE) {
|
||||
values.put(KeyRingData.KEY_RING_DATA, encodedKey);
|
||||
} else {
|
||||
values.put(KeyRingData.KEY_RING_DATA, (byte[]) null);
|
||||
}
|
||||
|
||||
Uri uri = KeyRingData.buildPublicKeyRingUri(masterKeyId);
|
||||
operations.add(ContentProviderOperation.newInsert(uri).withValues(values).build());
|
||||
}
|
||||
|
||||
private Uri writeSecretKeyRing(CanonicalizedSecretKeyRing keyRing, long masterKeyId) throws IOException {
|
||||
byte[] encodedKey = keyRing.getEncoded();
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(KeyRingData.MASTER_KEY_ID, masterKeyId);
|
||||
values.put(KeyRingData.KEY_RING_DATA, encodedKey);
|
||||
|
||||
// insert new version of this keyRing
|
||||
Uri uri = KeyRingData.buildSecretKeyRingUri(masterKeyId);
|
||||
return mContentResolver.insert(uri, values);
|
||||
}
|
||||
|
||||
private static class UserPacketItem implements Comparable<UserPacketItem> {
|
||||
Integer type;
|
||||
String userId;
|
||||
@@ -637,12 +667,8 @@ public class DatabaseReadWriteInteractor extends DatabaseInteractor {
|
||||
|
||||
// save secret keyring
|
||||
try {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(KeyRingData.MASTER_KEY_ID, masterKeyId);
|
||||
values.put(KeyRingData.KEY_RING_DATA, keyRing.getEncoded());
|
||||
// insert new version of this keyRing
|
||||
Uri uri = KeyRingData.buildSecretKeyRingUri(masterKeyId);
|
||||
if (mContentResolver.insert(uri, values) == null) {
|
||||
Uri insertedUri = writeSecretKeyRing(keyRing, masterKeyId);
|
||||
if (insertedUri == null) {
|
||||
log(LogType.MSG_IS_DB_EXCEPTION);
|
||||
return SaveKeyringResult.RESULT_ERROR;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user