package org.thialfihar.android.apg.provider; import java.io.IOException; import java.util.Date; import java.util.HashMap; import java.util.Vector; import org.bouncycastle2.openpgp.PGPException; import org.bouncycastle2.openpgp.PGPPublicKey; import org.bouncycastle2.openpgp.PGPPublicKeyRing; import org.bouncycastle2.openpgp.PGPSecretKey; import org.bouncycastle2.openpgp.PGPSecretKeyRing; import org.thialfihar.android.apg.Apg; import org.thialfihar.android.apg.Id; import org.thialfihar.android.apg.utils.IterableIterator; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; public class Database extends SQLiteOpenHelper { public static class GeneralException extends Exception { static final long serialVersionUID = 0xf812773343L; public GeneralException(String message) { super(message); } } private static final String DATABASE_NAME = "apg"; private static final int DATABASE_VERSION = 2; public static final String AUTHORITY = "org.thialfihar.android.apg.database"; public static HashMap<String, String> sKeyRingsProjection; public static HashMap<String, String> sKeysProjection; public static HashMap<String, String> sUserIdsProjection; private SQLiteDatabase mDb = null; private int mStatus = 0; static { sKeyRingsProjection = new HashMap<String, String>(); sKeyRingsProjection.put(KeyRings._ID, KeyRings._ID); sKeyRingsProjection.put(KeyRings.MASTER_KEY_ID, KeyRings.MASTER_KEY_ID); sKeyRingsProjection.put(KeyRings.TYPE, KeyRings.TYPE); sKeyRingsProjection.put(KeyRings.WHO_ID, KeyRings.WHO_ID); sKeyRingsProjection.put(KeyRings.KEY_RING_DATA, KeyRings.KEY_RING_DATA); sKeysProjection = new HashMap<String, String>(); sKeysProjection.put(Keys._ID, Keys._ID); sKeysProjection.put(Keys.KEY_ID, Keys.KEY_ID); sKeysProjection.put(Keys.TYPE, Keys.TYPE); sKeysProjection.put(Keys.IS_MASTER_KEY, Keys.IS_MASTER_KEY); sKeysProjection.put(Keys.ALGORITHM, Keys.ALGORITHM); sKeysProjection.put(Keys.KEY_SIZE, Keys.KEY_SIZE); sKeysProjection.put(Keys.CAN_SIGN, Keys.CAN_SIGN); sKeysProjection.put(Keys.CAN_ENCRYPT, Keys.CAN_ENCRYPT); sKeysProjection.put(Keys.IS_REVOKED, Keys.IS_REVOKED); sKeysProjection.put(Keys.CREATION, Keys.CREATION); sKeysProjection.put(Keys.EXPIRY, Keys.EXPIRY); sKeysProjection.put(Keys.KEY_DATA, Keys.KEY_DATA); sKeysProjection.put(Keys.RANK, Keys.RANK); sUserIdsProjection = new HashMap<String, String>(); sUserIdsProjection.put(UserIds._ID, UserIds._ID); sUserIdsProjection.put(UserIds.KEY_ID, UserIds.KEY_ID); sUserIdsProjection.put(UserIds.USER_ID, UserIds.USER_ID); sUserIdsProjection.put(UserIds.RANK, UserIds.RANK); } public Database(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); // force upgrade to test things //onUpgrade(getWritableDatabase(), 1, 2); mDb = getWritableDatabase(); } @Override protected void finalize() throws Throwable { mDb.close(); super.finalize(); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE " + KeyRings.TABLE_NAME + " (" + KeyRings._ID + " " + KeyRings._ID_type + "," + KeyRings.MASTER_KEY_ID + " " + KeyRings.MASTER_KEY_ID_type + ", " + KeyRings.TYPE + " " + KeyRings.TYPE_type + ", " + KeyRings.WHO_ID + " " + KeyRings.WHO_ID_type + ", " + KeyRings.KEY_RING_DATA + " " + KeyRings.KEY_RING_DATA_type + ");"); db.execSQL("CREATE TABLE " + Keys.TABLE_NAME + " (" + Keys._ID + " " + Keys._ID_type + "," + Keys.KEY_ID + " " + Keys.KEY_ID_type + ", " + Keys.TYPE + " " + Keys.TYPE_type + ", " + Keys.IS_MASTER_KEY + " " + Keys.IS_MASTER_KEY_type + ", " + Keys.ALGORITHM + " " + Keys.ALGORITHM_type + ", " + Keys.KEY_SIZE + " " + Keys.KEY_SIZE_type + ", " + Keys.CAN_SIGN + " " + Keys.CAN_SIGN_type + ", " + Keys.CAN_ENCRYPT + " " + Keys.CAN_ENCRYPT_type + ", " + Keys.IS_REVOKED + " " + Keys.IS_REVOKED_type + ", " + Keys.CREATION + " " + Keys.CREATION_type + ", " + Keys.EXPIRY + " " + Keys.EXPIRY_type + ", " + Keys.KEY_RING_ID + " " + Keys.KEY_RING_ID_type + ", " + Keys.KEY_DATA + " " + Keys.KEY_DATA_type + Keys.RANK + " " + Keys.RANK_type + ");"); db.execSQL("CREATE TABLE " + UserIds.TABLE_NAME + " (" + UserIds._ID + " " + UserIds._ID_type + "," + UserIds.KEY_ID + " " + UserIds.KEY_ID_type + "," + UserIds.USER_ID + " " + UserIds.USER_ID_type + "," + UserIds.RANK + " " + UserIds.RANK_type + ");"); db.execSQL("CREATE TABLE " + Accounts.TABLE_NAME + " (" + Accounts._ID + " " + Accounts._ID_type + "," + Accounts.NAME + " " + Accounts.NAME_type + ");"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { mDb = db; for (int version = oldVersion; version < newVersion; ++version) { switch (version) { case 1: { // upgrade 1 to 2 db.execSQL("DROP TABLE IF EXISTS " + KeyRings.TABLE_NAME + ";"); db.execSQL("DROP TABLE IF EXISTS " + Keys.TABLE_NAME + ";"); db.execSQL("DROP TABLE IF EXISTS " + UserIds.TABLE_NAME + ";"); db.execSQL("CREATE TABLE " + KeyRings.TABLE_NAME + " (" + KeyRings._ID + " " + KeyRings._ID_type + "," + KeyRings.MASTER_KEY_ID + " " + KeyRings.MASTER_KEY_ID_type + ", " + KeyRings.TYPE + " " + KeyRings.TYPE_type + ", " + KeyRings.WHO_ID + " " + KeyRings.WHO_ID_type + ", " + KeyRings.KEY_RING_DATA + " " + KeyRings.KEY_RING_DATA_type + ");"); db.execSQL("CREATE TABLE " + Keys.TABLE_NAME + " (" + Keys._ID + " " + Keys._ID_type + "," + Keys.KEY_ID + " " + Keys.KEY_ID_type + ", " + Keys.TYPE + " " + Keys.TYPE_type + ", " + Keys.IS_MASTER_KEY + " " + Keys.IS_MASTER_KEY_type + ", " + Keys.ALGORITHM + " " + Keys.ALGORITHM_type + ", " + Keys.KEY_SIZE + " " + Keys.KEY_SIZE_type + ", " + Keys.CAN_SIGN + " " + Keys.CAN_SIGN_type + ", " + Keys.CAN_ENCRYPT + " " + Keys.CAN_ENCRYPT_type + ", " + Keys.IS_REVOKED + " " + Keys.IS_REVOKED_type + ", " + Keys.CREATION + " " + Keys.CREATION_type + ", " + Keys.EXPIRY + " " + Keys.EXPIRY_type + ", " + Keys.KEY_RING_ID + " " + Keys.KEY_RING_ID_type + ", " + Keys.KEY_DATA + " " + Keys.KEY_DATA_type + Keys.RANK + " " + Keys.RANK_type + ");"); db.execSQL("CREATE TABLE " + UserIds.TABLE_NAME + " (" + UserIds._ID + " " + UserIds._ID_type + "," + UserIds.KEY_ID + " " + UserIds.KEY_ID_type + "," + UserIds.USER_ID + " " + UserIds.USER_ID_type + "," + UserIds.RANK + " " + UserIds.RANK_type + ");"); Cursor cursor = db.query("public_keys", new String[] { "c_key_data" }, null, null, null, null, null); if (cursor != null && cursor.moveToFirst()) { do { byte[] data = cursor.getBlob(0); try { PGPPublicKeyRing keyRing = new PGPPublicKeyRing(data); saveKeyRing(keyRing); } catch (IOException e) { Log.e("apg.db.upgrade", "key import failed: " + e); } catch (GeneralException e) { Log.e("apg.db.upgrade", "key import failed: " + e); } } while (cursor.moveToNext()); } if (cursor != null) { cursor.close(); } cursor = db.query("secret_keys", new String[]{ "c_key_data" }, null, null, null, null, null); if (cursor != null && cursor.moveToFirst()) { do { byte[] data = cursor.getBlob(0); try { PGPSecretKeyRing keyRing = new PGPSecretKeyRing(data); saveKeyRing(keyRing); } catch (IOException e) { Log.e("apg.db.upgrade", "key import failed: " + e); } catch (PGPException e) { Log.e("apg.db.upgrade", "key import failed: " + e); } catch (GeneralException e) { Log.e("apg.db.upgrade", "key import failed: " + e); } } while (cursor.moveToNext()); } if (cursor != null) { cursor.close(); } db.execSQL("DROP TABLE IF EXISTS public_keys;"); db.execSQL("DROP TABLE IF EXISTS secret_keys;"); break; } default: { break; } } } mDb = null; } public int saveKeyRing(PGPPublicKeyRing keyRing) throws IOException, GeneralException { mDb.beginTransaction(); ContentValues values = new ContentValues(); PGPPublicKey masterKey = keyRing.getPublicKey(); long masterKeyId = masterKey.getKeyID(); values.put(KeyRings.MASTER_KEY_ID, masterKeyId); values.put(KeyRings.TYPE, Id.database.type_public); values.put(KeyRings.KEY_RING_DATA, keyRing.getEncoded()); long rowId = insertOrUpdateKeyRing(values); int returnValue = mStatus; if (rowId == -1) { throw new GeneralException("saving public key ring " + masterKeyId + " failed"); } Vector<Integer> seenIds = new Vector<Integer>(); int rank = 0; for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(keyRing.getPublicKeys())) { seenIds.add(saveKey(rowId, key, rank)); ++rank; } String seenIdsStr = ""; for (Integer id : seenIds) { if (seenIdsStr.length() > 0) { seenIdsStr += ","; } seenIdsStr += id; } mDb.delete(Keys.TABLE_NAME, Keys.KEY_RING_ID + " = ? AND " + Keys._ID + " NOT IN (" + seenIdsStr + ")", new String[] { "" + rowId }); mDb.setTransactionSuccessful(); mDb.endTransaction(); return returnValue; } public int saveKeyRing(PGPSecretKeyRing keyRing) throws IOException, GeneralException { mDb.beginTransaction(); ContentValues values = new ContentValues(); PGPSecretKey masterKey = keyRing.getSecretKey(); long masterKeyId = masterKey.getKeyID(); values.put(KeyRings.MASTER_KEY_ID, masterKeyId); values.put(KeyRings.TYPE, Id.database.type_secret); values.put(KeyRings.KEY_RING_DATA, keyRing.getEncoded()); long rowId = insertOrUpdateKeyRing(values); int returnValue = mStatus; if (rowId == -1) { throw new GeneralException("saving secret key ring " + masterKeyId + " failed"); } Vector<Integer> seenIds = new Vector<Integer>(); int rank = 0; for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(keyRing.getSecretKeys())) { seenIds.add(saveKey(rowId, key, rank)); ++rank; } String seenIdsStr = ""; for (Integer id : seenIds) { if (seenIdsStr.length() > 0) { seenIdsStr += ","; } seenIdsStr += id; } mDb.delete(Keys.TABLE_NAME, Keys.KEY_RING_ID + " = ? AND " + Keys._ID + " NOT IN (" + seenIdsStr + ")", new String[] { "" + rowId }); mDb.setTransactionSuccessful(); mDb.endTransaction(); return returnValue; } private int saveKey(long keyRingId, PGPPublicKey key, int rank) throws IOException, GeneralException { ContentValues values = new ContentValues(); values.put(Keys.KEY_ID, key.getKeyID()); values.put(Keys.TYPE, Id.database.type_public); values.put(Keys.IS_MASTER_KEY, key.isMasterKey()); values.put(Keys.ALGORITHM, key.getAlgorithm()); values.put(Keys.KEY_SIZE, key.getBitStrength()); values.put(Keys.CAN_SIGN, Apg.isSigningKey(key)); values.put(Keys.CAN_ENCRYPT, Apg.isEncryptionKey(key)); values.put(Keys.IS_REVOKED, key.isRevoked()); values.put(Keys.CREATION, Apg.getCreationDate(key).getTime() / 1000); Date expiryDate = Apg.getExpiryDate(key); if (expiryDate != null) { values.put(Keys.EXPIRY, expiryDate.getTime() / 1000); } values.put(Keys.KEY_RING_ID, keyRingId); values.put(Keys.KEY_DATA, key.getEncoded()); values.put(Keys.RANK, rank); long rowId = insertOrUpdateKey(values); if (rowId == -1) { throw new GeneralException("saving public key " + key.getKeyID() + " failed"); } Vector<Integer> seenIds = new Vector<Integer>(); int userIdRank = 0; for (String userId : new IterableIterator<String>(key.getUserIDs())) { seenIds.add(saveUserId(rowId, userId, userIdRank)); ++userIdRank; } String seenIdsStr = ""; for (Integer id : seenIds) { if (seenIdsStr.length() > 0) { seenIdsStr += ","; } seenIdsStr += id; } mDb.delete(UserIds.TABLE_NAME, UserIds.KEY_ID + " = ? AND " + UserIds._ID + " NOT IN (" + seenIdsStr + ")", new String[] { "" + rowId }); return (int)rowId; } private int saveKey(long keyRingId, PGPSecretKey key, int rank) throws IOException, GeneralException { ContentValues values = new ContentValues(); values.put(Keys.KEY_ID, key.getPublicKey().getKeyID()); values.put(Keys.TYPE, Id.database.type_secret); values.put(Keys.IS_MASTER_KEY, key.isMasterKey()); values.put(Keys.ALGORITHM, key.getPublicKey().getAlgorithm()); values.put(Keys.KEY_SIZE, key.getPublicKey().getBitStrength()); values.put(Keys.CAN_SIGN, Apg.isSigningKey(key)); values.put(Keys.CAN_ENCRYPT, Apg.isEncryptionKey(key)); values.put(Keys.IS_REVOKED, key.getPublicKey().isRevoked()); values.put(Keys.CREATION, Apg.getCreationDate(key).getTime() / 1000); Date expiryDate = Apg.getExpiryDate(key); if (expiryDate != null) { values.put(Keys.EXPIRY, expiryDate.getTime() / 1000); } values.put(Keys.KEY_RING_ID, keyRingId); values.put(Keys.KEY_DATA, key.getEncoded()); values.put(Keys.RANK, rank); long rowId = insertOrUpdateKey(values); if (rowId == -1) { throw new GeneralException("saving secret key " + key.getPublicKey().getKeyID() + " failed"); } Vector<Integer> seenIds = new Vector<Integer>(); int userIdRank = 0; for (String userId : new IterableIterator<String>(key.getUserIDs())) { seenIds.add(saveUserId(rowId, userId, userIdRank)); ++userIdRank; } String seenIdsStr = ""; for (Integer id : seenIds) { if (seenIdsStr.length() > 0) { seenIdsStr += ","; } seenIdsStr += id; } mDb.delete(UserIds.TABLE_NAME, UserIds.KEY_ID + " = ? AND " + UserIds._ID + " NOT IN (" + seenIdsStr + ")", new String[] { "" + rowId }); return (int)rowId; } private int saveUserId(long keyId, String userId, int rank) throws GeneralException { ContentValues values = new ContentValues(); values.put(UserIds.KEY_ID, keyId); values.put(UserIds.USER_ID, userId); values.put(UserIds.RANK, rank); long rowId = insertOrUpdateUserId(values); if (rowId == -1) { throw new GeneralException("saving user id " + userId + " failed"); } return (int)rowId; } private long insertOrUpdateKeyRing(ContentValues values) { Cursor c = mDb.query(KeyRings.TABLE_NAME, new String[] { KeyRings._ID }, KeyRings.MASTER_KEY_ID + " = ? AND " + KeyRings.TYPE + " = ?", new String[] { values.getAsString(KeyRings.MASTER_KEY_ID), values.getAsString(KeyRings.TYPE), }, null, null, null); long rowId = -1; if (c != null && c.moveToFirst()) { rowId = c.getLong(0); mDb.update(KeyRings.TABLE_NAME, values, KeyRings._ID + " = ?", new String[] { "" + rowId }); mStatus = Id.return_value.updated; } else { rowId = mDb.insert(KeyRings.TABLE_NAME, KeyRings.WHO_ID, values); mStatus = Id.return_value.ok; } if (c != null) { c.close(); } return rowId; } private long insertOrUpdateKey(ContentValues values) { Cursor c = mDb.query(Keys.TABLE_NAME, new String[] { Keys._ID }, Keys.KEY_ID + " = ? AND " + Keys.TYPE + " = ?", new String[] { values.getAsString(Keys.KEY_ID), values.getAsString(Keys.TYPE), }, null, null, null); long rowId = -1; if (c != null && c.moveToFirst()) { rowId = c.getLong(0); mDb.update(Keys.TABLE_NAME, values, Keys._ID + " = ?", new String[] { "" + rowId }); } else { rowId = mDb.insert(Keys.TABLE_NAME, Keys.KEY_DATA, values); } if (c != null) { c.close(); } return rowId; } private long insertOrUpdateUserId(ContentValues values) { Cursor c = mDb.query(UserIds.TABLE_NAME, new String[] { UserIds._ID }, UserIds.KEY_ID + " = ? AND " + UserIds.USER_ID + " = ?", new String[] { values.getAsString(UserIds.KEY_ID), values.getAsString(UserIds.USER_ID), }, null, null, null); long rowId = -1; if (c != null && c.moveToFirst()) { rowId = c.getLong(0); mDb.update(UserIds.TABLE_NAME, values, UserIds._ID + " = ?", new String[] { "" + rowId }); } else { rowId = mDb.insert(UserIds.TABLE_NAME, UserIds.USER_ID, values); } if (c != null) { c.close(); } return rowId; } public Object getKeyRing(int keyRingId) { Cursor c = mDb.query(KeyRings.TABLE_NAME, new String[] { KeyRings.KEY_RING_DATA, KeyRings.TYPE }, KeyRings._ID + " = ?", new String[] { "" + keyRingId, }, null, null, null); byte[] data = null; Object keyRing = null; if (c != null && c.moveToFirst()) { data = c.getBlob(0); if (data != null) { try { if (c.getInt(1) == Id.database.type_public) { keyRing = new PGPPublicKeyRing(data); } else { keyRing = new PGPSecretKeyRing(data); } } catch (IOException e) { // can't load it, then } catch (PGPException e) { // can't load it, then } } } if (c != null) { c.close(); } return keyRing; } public byte[] getKeyRingDataFromKeyId(int type, long keyId) { Cursor c = mDb.query(Keys.TABLE_NAME + " INNER JOIN " + KeyRings.TABLE_NAME + " ON (" + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + ")", new String[] { KeyRings.TABLE_NAME + "." + KeyRings.KEY_RING_DATA }, Keys.TABLE_NAME + "." + Keys.KEY_ID + " = ? AND " + KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?", new String[] { "" + keyId, "" + type, }, null, null, null); byte[] data = null; if (c != null && c.moveToFirst()) { data = c.getBlob(0); } if (c != null) { c.close(); } return data; } public byte[] getKeyDataFromKeyId(int type, long keyId) { Cursor c = mDb.query(Keys.TABLE_NAME, new String[] { Keys.KEY_DATA }, Keys.KEY_ID + " = ? AND " + Keys.TYPE + " = ?", new String[] { "" + keyId, "" + type, }, null, null, null); byte[] data = null; if (c != null && c.moveToFirst()) { data = c.getBlob(0); } if (c != null) { c.close(); } return data; } public void deleteKeyRing(int keyRingId) { mDb.beginTransaction(); mDb.delete(KeyRings.TABLE_NAME, KeyRings._ID + " = ?", new String[] { "" + keyRingId }); Cursor c = mDb.query(Keys.TABLE_NAME, new String[] { Keys._ID }, Keys.KEY_RING_ID + " = ?", new String[] { "" + keyRingId, }, null, null, null); if (c != null && c.moveToFirst()) { do { int keyId = c.getInt(0); deleteKey(keyId); } while (c.moveToNext()); } if (c != null) { c.close(); } mDb.setTransactionSuccessful(); mDb.endTransaction(); } private void deleteKey(int keyId) { mDb.delete(Keys.TABLE_NAME, Keys._ID + " = ?", new String[] { "" + keyId }); mDb.delete(UserIds.TABLE_NAME, UserIds.KEY_ID + " = ?", new String[] { "" + keyId }); } public SQLiteDatabase db() { return mDb; } }