diff options
Diffstat (limited to 'OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider')
4 files changed, 309 insertions, 42 deletions
diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index 9eeb57222..a029da478 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -52,6 +52,17 @@ public class KeychainContract { String USER_ID = "user_id"; // not a database id String RANK = "rank"; // ONLY used for sorting! no key, no nothing! String IS_PRIMARY = "is_primary"; + String IS_REVOKED = "is_revoked"; + } + + interface CertsColumns { + String MASTER_KEY_ID = "master_key_id"; + String RANK = "rank"; + String KEY_ID_CERTIFIER = "key_id_certifier"; + String TYPE = "type"; + String VERIFIED = "verified"; + String CREATION = "creation"; + String DATA = "data"; } interface ApiAppsColumns { @@ -91,12 +102,14 @@ public class KeychainContract { public static final String PATH_SECRET = "secret"; public static final String PATH_USER_IDS = "user_ids"; public static final String PATH_KEYS = "keys"; + public static final String PATH_CERTS = "certs"; public static final String BASE_API_APPS = "api_apps"; public static final String PATH_ACCOUNTS = "accounts"; public static class KeyRings implements BaseColumns, KeysColumns, UserIdsColumns { - public static final String MASTER_KEY_ID = "master_key_id"; + public static final String MASTER_KEY_ID = KeysColumns.MASTER_KEY_ID; + public static final String IS_REVOKED = KeysColumns.IS_REVOKED; public static final String HAS_SECRET = "has_secret"; public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() @@ -145,6 +158,9 @@ public class KeychainContract { return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_PUBLIC).build(); } + public static Uri buildSecretKeyRingUri() { + return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).build(); + } public static Uri buildSecretKeyRingUri(String masterKeyId) { return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_SECRET).build(); } @@ -178,6 +194,7 @@ public class KeychainContract { } public static class UserIds implements UserIdsColumns, BaseColumns { + public static final String VERIFIED = "verified"; public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() .appendPath(BASE_KEY_RINGS).build(); @@ -243,6 +260,28 @@ public class KeychainContract { } } + public static class Certs implements CertsColumns, BaseColumns { + public static final String USER_ID = UserIdsColumns.USER_ID; + public static final String SIGNER_UID = "signer_user_id"; + + public static final int VERIFIED_SECRET = 1; + public static final int VERIFIED_SELF = 2; + + public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() + .appendPath(BASE_KEY_RINGS).build(); + + public static Uri buildCertsUri(String masterKeyId) { + return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_CERTS).build(); + } + public static Uri buildCertsSpecificUri(String masterKeyId, String rank, String certifier) { + return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_CERTS).appendPath(rank).appendPath(certifier).build(); + } + public static Uri buildCertsUri(Uri uri) { + return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_CERTS).build(); + } + + } + public static class DataStream { public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() .appendPath(BASE_DATA).build(); diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index 36e2a7962..7fbfe1d60 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -30,14 +30,13 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.PgpConversionHelper; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsAccountsColumns; -import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.UserIdsColumns; +import org.sufficientlysecure.keychain.provider.KeychainContract.CertsColumns; import org.sufficientlysecure.keychain.util.Log; import java.io.IOException; -import java.util.Arrays; public class KeychainDatabase extends SQLiteOpenHelper { private static final String DATABASE_NAME = "openkeychain.db"; @@ -49,6 +48,7 @@ public class KeychainDatabase extends SQLiteOpenHelper { String KEY_RINGS_SECRET = "keyrings_secret"; String KEYS = "keys"; String USER_IDS = "user_ids"; + String CERTS = "certs"; String API_APPS = "api_apps"; String API_ACCOUNTS = "api_accounts"; } @@ -96,13 +96,35 @@ public class KeychainDatabase extends SQLiteOpenHelper { + UserIdsColumns.USER_ID + " CHARMANDER, " + UserIdsColumns.IS_PRIMARY + " BOOLEAN, " + + UserIdsColumns.IS_REVOKED + " BOOLEAN, " + UserIdsColumns.RANK+ " INTEGER, " - + "PRIMARY KEY(" + UserIdsColumns.MASTER_KEY_ID + ", " + UserIdsColumns.USER_ID + ")," + + "PRIMARY KEY(" + UserIdsColumns.MASTER_KEY_ID + ", " + UserIdsColumns.USER_ID + "), " + + "UNIQUE (" + UserIdsColumns.MASTER_KEY_ID + ", " + UserIdsColumns.RANK + "), " + "FOREIGN KEY(" + UserIdsColumns.MASTER_KEY_ID + ") REFERENCES " + Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE" + ")"; + private static final String CREATE_CERTS = + "CREATE TABLE IF NOT EXISTS " + Tables.CERTS + "(" + + CertsColumns.MASTER_KEY_ID + " INTEGER," + + CertsColumns.RANK + " INTEGER, " // rank of certified uid + + + CertsColumns.KEY_ID_CERTIFIER + " INTEGER, " // certifying key + + CertsColumns.TYPE + " INTEGER, " + + CertsColumns.VERIFIED + " INTEGER, " + + CertsColumns.CREATION + " INTEGER, " + + + CertsColumns.DATA + " BLOB, " + + + "PRIMARY KEY(" + CertsColumns.MASTER_KEY_ID + ", " + CertsColumns.RANK + ", " + + CertsColumns.KEY_ID_CERTIFIER + "), " + + "FOREIGN KEY(" + CertsColumns.MASTER_KEY_ID + ") REFERENCES " + + Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE," + + "FOREIGN KEY(" + CertsColumns.MASTER_KEY_ID + ", " + CertsColumns.RANK + ") REFERENCES " + + Tables.USER_IDS + "(" + UserIdsColumns.MASTER_KEY_ID + ", " + UserIdsColumns.RANK + ") ON DELETE CASCADE" + + ")"; + private static final String CREATE_API_APPS = "CREATE TABLE IF NOT EXISTS " + Tables.API_APPS + " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + ApiAppsColumns.PACKAGE_NAME + " TEXT NOT NULL UNIQUE, " @@ -145,6 +167,7 @@ public class KeychainDatabase extends SQLiteOpenHelper { db.execSQL(CREATE_KEYRINGS_SECRET); db.execSQL(CREATE_KEYS); db.execSQL(CREATE_USER_IDS); + db.execSQL(CREATE_CERTS); db.execSQL(CREATE_API_APPS); db.execSQL(CREATE_API_APPS_ACCOUNTS); } @@ -155,6 +178,11 @@ public class KeychainDatabase extends SQLiteOpenHelper { if (!db.isReadOnly()) { // Enable foreign key constraints db.execSQL("PRAGMA foreign_keys=ON;"); + // TODO remove, once we remove the "always migrate" debug stuff + // db.execSQL("DROP TABLE certs;"); + // db.execSQL("DROP TABLE user_ids;"); + db.execSQL(CREATE_USER_IDS); + db.execSQL(CREATE_CERTS); } } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index 1dd6ab08f..72cb53e76 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -34,6 +34,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; +import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.util.Log; @@ -45,12 +46,15 @@ public class KeychainProvider extends ContentProvider { private static final int KEY_RINGS_UNIFIED = 101; private static final int KEY_RINGS_PUBLIC = 102; + private static final int KEY_RINGS_SECRET = 103; private static final int KEY_RING_UNIFIED = 200; 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; private static final int KEY_RING_SECRET = 204; + private static final int KEY_RING_CERTS = 205; + private static final int KEY_RING_CERTS_SPECIFIC = 206; private static final int API_APPS = 301; private static final int API_APPS_BY_PACKAGE_NAME = 303; @@ -87,6 +91,9 @@ public class KeychainProvider extends ContentProvider { matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + KeychainContract.PATH_PUBLIC, KEY_RINGS_PUBLIC); + matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + + "/" + KeychainContract.PATH_SECRET, + KEY_RINGS_SECRET); /** * find by criteria other than master key id @@ -111,6 +118,8 @@ public class KeychainProvider extends ContentProvider { * key_rings/_/user_ids * key_rings/_/public * key_rings/_/secret + * key_rings/_/certs + * key_rings/_/certs/_/_ * </pre> */ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" @@ -128,6 +137,12 @@ public class KeychainProvider extends ContentProvider { matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" + KeychainContract.PATH_SECRET, KEY_RING_SECRET); + matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" + + KeychainContract.PATH_CERTS, + KEY_RING_CERTS); + matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" + + KeychainContract.PATH_CERTS + "/*/*", + KEY_RING_CERTS_SPECIFIC); /** * API apps @@ -238,7 +253,7 @@ public class KeychainProvider extends ContentProvider { projectionMap.put(KeyRings.MASTER_KEY_ID, Tables.KEYS + "." + Keys.MASTER_KEY_ID); projectionMap.put(KeyRings.KEY_ID, Keys.KEY_ID); projectionMap.put(KeyRings.KEY_SIZE, Keys.KEY_SIZE); - projectionMap.put(KeyRings.IS_REVOKED, Keys.IS_REVOKED); + projectionMap.put(KeyRings.IS_REVOKED, Tables.KEYS + "." + Keys.IS_REVOKED); projectionMap.put(KeyRings.CAN_CERTIFY, Keys.CAN_CERTIFY); projectionMap.put(KeyRings.CAN_ENCRYPT, Keys.CAN_ENCRYPT); projectionMap.put(KeyRings.CAN_SIGN, Keys.CAN_SIGN); @@ -358,18 +373,30 @@ public class KeychainProvider extends ContentProvider { case KEY_RING_USER_IDS: { HashMap<String, String> projectionMap = new HashMap<String, String>(); projectionMap.put(UserIds._ID, Tables.USER_IDS + ".oid AS _id"); - projectionMap.put(UserIds.MASTER_KEY_ID, UserIds.MASTER_KEY_ID); - projectionMap.put(UserIds.USER_ID, UserIds.USER_ID); - projectionMap.put(UserIds.RANK, UserIds.RANK); - projectionMap.put(UserIds.IS_PRIMARY, UserIds.IS_PRIMARY); + projectionMap.put(UserIds.MASTER_KEY_ID, Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID); + projectionMap.put(UserIds.USER_ID, Tables.USER_IDS + "." + UserIds.USER_ID); + projectionMap.put(UserIds.RANK, Tables.USER_IDS + "." + UserIds.RANK); + projectionMap.put(UserIds.IS_PRIMARY, Tables.USER_IDS + "." + UserIds.IS_PRIMARY); + projectionMap.put(UserIds.IS_REVOKED, Tables.USER_IDS + "." + UserIds.IS_REVOKED); + // we take the minimum (>0) here, where "1" is "verified by known secret key" + projectionMap.put(UserIds.VERIFIED, "MIN(" + Certs.VERIFIED + ") AS " + UserIds.VERIFIED); qb.setProjectionMap(projectionMap); - qb.setTables(Tables.USER_IDS); - qb.appendWhere(UserIds.MASTER_KEY_ID + " = "); + qb.setTables(Tables.USER_IDS + + " LEFT JOIN " + Tables.CERTS + " ON (" + + Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID + " = " + + Tables.CERTS + "." + Certs.MASTER_KEY_ID + + " AND " + Tables.USER_IDS + "." + UserIds.RANK + " = " + + Tables.CERTS + "." + Certs.RANK + + " AND " + Tables.CERTS + "." + Certs.VERIFIED + " > 0" + + ")"); + groupBy = Tables.USER_IDS + "." + UserIds.RANK; + + qb.appendWhere(Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID + " = "); qb.appendWhereEscapeString(uri.getPathSegments().get(1)); if (TextUtils.isEmpty(sortOrder)) { - sortOrder = UserIds.RANK + " ASC"; + sortOrder = Tables.USER_IDS + "." + UserIds.RANK + " ASC"; } break; @@ -394,6 +421,7 @@ public class KeychainProvider extends ContentProvider { break; } + case KEY_RINGS_SECRET: case KEY_RING_SECRET: { HashMap<String, String> projectionMap = new HashMap<String, String>(); projectionMap.put(KeyRingData._ID, Tables.KEY_RINGS_SECRET + ".oid AS _id"); @@ -402,8 +430,55 @@ public class KeychainProvider extends ContentProvider { qb.setProjectionMap(projectionMap); qb.setTables(Tables.KEY_RINGS_SECRET); - qb.appendWhere(KeyRings.MASTER_KEY_ID + " = "); + + if(match == KEY_RING_SECRET) { + qb.appendWhere(KeyRings.MASTER_KEY_ID + " = "); + qb.appendWhereEscapeString(uri.getPathSegments().get(1)); + } + + break; + } + + case KEY_RING_CERTS: + case KEY_RING_CERTS_SPECIFIC: { + HashMap<String, String> projectionMap = new HashMap<String, String>(); + projectionMap.put(Certs._ID, Tables.CERTS + ".oid AS " + Certs._ID); + projectionMap.put(Certs.MASTER_KEY_ID, Tables.CERTS + "." + Certs.MASTER_KEY_ID); + projectionMap.put(Certs.RANK, Tables.CERTS + "." + Certs.RANK); + projectionMap.put(Certs.VERIFIED, Tables.CERTS + "." + Certs.VERIFIED); + projectionMap.put(Certs.TYPE, Tables.CERTS + "." + Certs.TYPE); + projectionMap.put(Certs.CREATION, Tables.CERTS + "." + Certs.CREATION); + projectionMap.put(Certs.KEY_ID_CERTIFIER, Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER); + projectionMap.put(Certs.DATA, Tables.CERTS + "." + Certs.DATA); + projectionMap.put(Certs.USER_ID, Tables.USER_IDS + "." + UserIds.USER_ID); + projectionMap.put(Certs.SIGNER_UID, "signer." + UserIds.USER_ID + " AS " + Certs.SIGNER_UID); + qb.setProjectionMap(projectionMap); + + qb.setTables(Tables.CERTS + + " JOIN " + Tables.USER_IDS + " ON (" + + Tables.CERTS + "." + Certs.MASTER_KEY_ID + " = " + + Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID + + " AND " + + Tables.CERTS + "." + Certs.RANK + " = " + + Tables.USER_IDS + "." + UserIds.RANK + + ") LEFT JOIN " + Tables.USER_IDS + " AS signer ON (" + + Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER + " = " + + "signer." + UserIds.MASTER_KEY_ID + + " AND " + + "signer." + Keys.RANK + " = 0" + + ")"); + + groupBy = Tables.CERTS + "." + Certs.RANK + ", " + + Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER; + + qb.appendWhere(Tables.CERTS + "." + Certs.MASTER_KEY_ID + " = "); qb.appendWhereEscapeString(uri.getPathSegments().get(1)); + if(match == KEY_RING_CERTS_SPECIFIC) { + qb.appendWhere(" AND " + Tables.CERTS + "." + Certs.RANK + " = "); + qb.appendWhereEscapeString(uri.getPathSegments().get(3)); + qb.appendWhere(" AND " + Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER+ " = "); + qb.appendWhereEscapeString(uri.getPathSegments().get(4)); + } break; } @@ -499,6 +574,13 @@ public class KeychainProvider extends ContentProvider { keyId = values.getAsLong(UserIds.MASTER_KEY_ID); break; + case KEY_RING_CERTS: + // we replace here, keeping only the latest signature + // TODO this would be better handled in saveKeyRing directly! + db.replaceOrThrow(Tables.CERTS, null, values); + keyId = values.getAsLong(Certs.MASTER_KEY_ID); + break; + case API_APPS: db.insertOrThrow(Tables.API_APPS, null, values); break; diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java index 3d1d663c7..e9179f864 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -17,6 +17,8 @@ package org.sufficientlysecure.keychain.provider; +import java.security.SignatureException; +import org.spongycastle.bcpg.ArmoredOutputStream; import android.content.ContentProviderOperation; import android.content.ContentResolver; import android.content.ContentValues; @@ -27,11 +29,13 @@ import android.database.DatabaseUtils; import android.net.Uri; import android.os.RemoteException; -import org.spongycastle.bcpg.ArmoredOutputStream; +import org.spongycastle.bcpg.SignatureSubpacketTags; +import org.spongycastle.bcpg.sig.SignatureExpirationTime; +import org.spongycastle.openpgp.PGPException; +import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; import org.spongycastle.openpgp.PGPKeyRing; import org.spongycastle.openpgp.PGPPublicKey; import org.spongycastle.openpgp.PGPPublicKeyRing; -import org.spongycastle.openpgp.PGPSecretKey; import org.spongycastle.openpgp.PGPSecretKeyRing; import org.spongycastle.openpgp.PGPSignature; import org.sufficientlysecure.keychain.Constants; @@ -43,6 +47,7 @@ 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.KeychainContract.UserIds; +import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.remote.AccountSettings; import org.sufficientlysecure.keychain.remote.AppSettings; import org.sufficientlysecure.keychain.util.IterableIterator; @@ -51,8 +56,11 @@ import org.sufficientlysecure.keychain.util.Log; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.HashSet; import java.util.Set; @@ -123,25 +131,31 @@ public class ProviderHelper { return 0L; } - public static PGPKeyRing getPGPKeyRing(Context context, Uri queryUri) { + public static Map<Long, PGPKeyRing> getPGPKeyRings(Context context, Uri queryUri) { Cursor cursor = context.getContentResolver().query(queryUri, - new String[]{KeyRings._ID, KeyRingData.KEY_RING_DATA}, null, null, null); + new String[]{KeyRingData.MASTER_KEY_ID, KeyRingData.KEY_RING_DATA }, + null, null, null); - PGPKeyRing keyRing = null; - if (cursor != null && cursor.moveToFirst()) { - int keyRingDataCol = cursor.getColumnIndex(KeyRingData.KEY_RING_DATA); - - byte[] data = cursor.getBlob(keyRingDataCol); + Map<Long, PGPKeyRing> result = new HashMap<Long, PGPKeyRing>(cursor.getCount()); + if (cursor != null && cursor.moveToFirst()) do { + long masterKeyId = cursor.getLong(0); + byte[] data = cursor.getBlob(1); if (data != null) { - keyRing = PgpConversionHelper.BytesToPGPKeyRing(data); + result.put(masterKeyId, PgpConversionHelper.BytesToPGPKeyRing(data)); } - } + } while(cursor.moveToNext()); if (cursor != null) { cursor.close(); } - return keyRing; + return result; + } + public static PGPKeyRing getPGPKeyRing(Context context, Uri queryUri) { + Map<Long, PGPKeyRing> result = getPGPKeyRings(context, queryUri); + if(result.isEmpty()) + return null; + return result.values().iterator().next(); } public static PGPPublicKeyRing getPGPPublicKeyRingWithKeyId(Context context, long keyId) { @@ -216,19 +230,88 @@ public class ProviderHelper { ++rank; } - int userIdRank = 0; + // get a list of owned secret keys, for verification filtering + Map<Long, PGPKeyRing> allKeyRings = getPGPKeyRings(context, KeyRingData.buildSecretKeyRingUri()); + // special case: available secret keys verify themselves! + if(secretRing != null) + allKeyRings.put(secretRing.getSecretKey().getKeyID(), secretRing); + + // classify and order user ids. primary are moved to the front, revoked to the back, + // otherwise the order in the keyfile is preserved. + List<UserIdItem> uids = new ArrayList<UserIdItem>(); + for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) { - operations.add(buildUserIdOperations(context, masterKeyId, userId, userIdRank)); - ++userIdRank; + UserIdItem item = new UserIdItem(); + uids.add(item); + item.userId = userId; + + // look through signatures for this specific key + for (PGPSignature cert : new IterableIterator<PGPSignature>( + masterKey.getSignaturesForID(userId))) { + long certId = cert.getKeyID(); + try { + // self signature + if(certId == masterKeyId) { + cert.init(new JcaPGPContentVerifierBuilderProvider().setProvider( + Constants.BOUNCY_CASTLE_PROVIDER_NAME), masterKey); + if(!cert.verifyCertification(userId, masterKey)) { + // not verified?! dang! TODO notify user? this is kinda serious... + Log.e(Constants.TAG, "Could not verify self signature for " + userId + "!"); + continue; + } + // is this the first, or a more recent certificate? + if(item.selfCert == null || + item.selfCert.getCreationTime().before(cert.getCreationTime())) { + item.selfCert = cert; + item.isPrimary = cert.getHashedSubPackets().isPrimaryUserID(); + item.isRevoked = + cert.getSignatureType() == PGPSignature.CERTIFICATION_REVOCATION; + } + } + // verify signatures from known private keys + if(allKeyRings.containsKey(certId)) { + // mark them as verified + cert.init(new JcaPGPContentVerifierBuilderProvider().setProvider( + Constants.BOUNCY_CASTLE_PROVIDER_NAME), + allKeyRings.get(certId).getPublicKey()); + if(cert.verifyCertification(userId, masterKey)) { + item.trustedCerts.add(cert); + } + } + } catch(SignatureException e) { + Log.e(Constants.TAG, "Signature verification failed! " + + PgpKeyHelper.convertKeyIdToHex(masterKey.getKeyID()) + + " from " + + PgpKeyHelper.convertKeyIdToHex(cert.getKeyID()), e); + } catch(PGPException e) { + Log.e(Constants.TAG, "Signature verification failed! " + + PgpKeyHelper.convertKeyIdToHex(masterKey.getKeyID()) + + " from " + + PgpKeyHelper.convertKeyIdToHex(cert.getKeyID()), e); + } + } } - for (PGPSignature certification : - new IterableIterator<PGPSignature>( - masterKey.getSignaturesOfType(PGPSignature.POSITIVE_CERTIFICATION))) { - // TODO: how to do this?? - // we need to verify the signatures again and again when they are displayed... -// if (certification.verify -// operations.add(buildPublicKeyOperations(context, keyRingRowId, key, rank)); + // primary before regular before revoked (see UserIdItem.compareTo) + // this is a stable sort, so the order of keys is otherwise preserved. + Collections.sort(uids); + // iterate and put into db + for(int userIdRank = 0; userIdRank < uids.size(); userIdRank++) { + UserIdItem item = uids.get(userIdRank); + operations.add(buildUserIdOperations(masterKeyId, item, userIdRank)); + // no self cert is bad, but allowed by the rfc... + if(item.selfCert != null) { + operations.add(buildCertOperations( + masterKeyId, userIdRank, item.selfCert, Certs.VERIFIED_SELF)); + } + // don't bother with trusted certs if the uid is revoked, anyways + if(item.isRevoked) { + continue; + } + for(int i = 0; i < item.trustedCerts.size(); i++) { + operations.add(buildCertOperations( + masterKeyId, userIdRank, item.trustedCerts.get(i), Certs.VERIFIED_SECRET)); + } } try { @@ -246,6 +329,25 @@ public class ProviderHelper { } + private static class UserIdItem implements Comparable<UserIdItem> { + String userId; + boolean isPrimary = false; + boolean isRevoked = false; + PGPSignature selfCert; + List<PGPSignature> trustedCerts = new ArrayList<PGPSignature>(); + + @Override + public int compareTo(UserIdItem o) { + // if one key is primary but the other isn't, the primary one always comes first + if(isPrimary != o.isPrimary) + return isPrimary ? -1 : 1; + // revoked keys always come last! + if(isRevoked != o.isRevoked) + return isRevoked ? 1 : -1; + return 0; + } + } + /** * Saves a PGPSecretKeyRing in the DB. This will only work if a corresponding public keyring * is already in the database! @@ -311,21 +413,37 @@ public class ProviderHelper { } /** - * Build ContentProviderOperation to add PGPSecretKey to database corresponding to a keyRing + * Build ContentProviderOperation to add PGPPublicKey to database corresponding to a keyRing */ - private static ContentProviderOperation buildSecretKeyOperations(Context context, - long masterKeyId, PGPSecretKey key, int rank) throws IOException { - return buildPublicKeyOperations(context, masterKeyId, key.getPublicKey(), rank); + private static ContentProviderOperation buildCertOperations(long masterKeyId, + int rank, + PGPSignature cert, + int verified) + throws IOException { + ContentValues values = new ContentValues(); + values.put(Certs.MASTER_KEY_ID, masterKeyId); + values.put(Certs.RANK, rank); + values.put(Certs.KEY_ID_CERTIFIER, cert.getKeyID()); + values.put(Certs.TYPE, cert.getSignatureType()); + values.put(Certs.CREATION, cert.getCreationTime().getTime() / 1000); + values.put(Certs.VERIFIED, verified); + values.put(Certs.DATA, cert.getEncoded()); + + Uri uri = Certs.buildCertsUri(Long.toString(masterKeyId)); + + return ContentProviderOperation.newInsert(uri).withValues(values).build(); } /** * Build ContentProviderOperation to add PublicUserIds to database corresponding to a keyRing */ - private static ContentProviderOperation buildUserIdOperations(Context context, - long masterKeyId, String userId, int rank) { + private static ContentProviderOperation buildUserIdOperations(long masterKeyId, UserIdItem item, + int rank) { ContentValues values = new ContentValues(); values.put(UserIds.MASTER_KEY_ID, masterKeyId); - values.put(UserIds.USER_ID, userId); + values.put(UserIds.USER_ID, item.userId); + values.put(UserIds.IS_PRIMARY, item.isPrimary); + values.put(UserIds.IS_REVOKED, item.isRevoked); values.put(UserIds.RANK, rank); Uri uri = UserIds.buildUserIdsUri(Long.toString(masterKeyId)); |