aboutsummaryrefslogtreecommitdiffstats
path: root/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider
diff options
context:
space:
mode:
Diffstat (limited to 'OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider')
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java41
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java34
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java100
-rw-r--r--OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java176
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));