diff options
author | mar-v-in <github@rvin.mooo.com> | 2014-06-04 21:32:37 +0200 |
---|---|---|
committer | mar-v-in <github@rvin.mooo.com> | 2014-06-04 21:32:37 +0200 |
commit | cae0071342e746c934490298c3dd3ee230a2ee32 (patch) | |
tree | 5c540d7c8ad84b462fd3cbc87b0aea7c5513fc9c /OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider | |
parent | 6a637462782b4ce57ecf154edf0974114181b8ad (diff) | |
parent | 52f1f30846ad7efa6e6ae11ed96f5b68626bfb3b (diff) | |
download | open-keychain-cae0071342e746c934490298c3dd3ee230a2ee32.tar.gz open-keychain-cae0071342e746c934490298c3dd3ee230a2ee32.tar.bz2 open-keychain-cae0071342e746c934490298c3dd3ee230a2ee32.zip |
Merge branch 'master' into automatic-contact-discovery
Conflicts:
OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider')
5 files changed, 369 insertions, 269 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java new file mode 100644 index 000000000..48d40430a --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java @@ -0,0 +1,161 @@ +package org.sufficientlysecure.keychain.provider; + +import android.net.Uri; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.util.Log; + +/** This implementation of KeyRing provides a cached view of PublicKeyRing + * objects based on database queries exclusively. + * + * This class should be used where only few points of data but no actual + * cryptographic operations are required about a PublicKeyRing which is already + * in the database. This happens commonly in UI code, where parsing of a PGP + * key for examination would be a very expensive operation. + * + * Each getter method is implemented using a more or less expensive database + * query, while object construction is (almost) free. A common pattern is + * mProviderHelper.getCachedKeyRing(uri).getterMethod() + * + * TODO Ensure that the values returned here always match the ones returned by + * the parsed KeyRing! + * + */ +public class CachedPublicKeyRing extends KeyRing { + + final ProviderHelper mProviderHelper; + final Uri mUri; + + public CachedPublicKeyRing(ProviderHelper providerHelper, Uri uri) { + mProviderHelper = providerHelper; + mUri = uri; + } + + public long getMasterKeyId() throws PgpGeneralException { + try { + Object data = mProviderHelper.getGenericData(mUri, + KeychainContract.KeyRings.MASTER_KEY_ID, ProviderHelper.FIELD_TYPE_INTEGER); + return (Long) data; + } catch (ProviderHelper.NotFoundException e) { + throw new PgpGeneralException(e); + } + } + + /** + * Find the master key id related to a given query. The id will either be extracted from the + * query, which should work for all specific /key_rings/ queries, or will be queried if it can't. + */ + public long extractOrGetMasterKeyId() throws PgpGeneralException { + // try extracting from the uri first + String firstSegment = mUri.getPathSegments().get(1); + if (!firstSegment.equals("find")) try { + return Long.parseLong(firstSegment); + } catch (NumberFormatException e) { + // didn't work? oh well. + Log.d(Constants.TAG, "Couldn't get masterKeyId from URI, querying..."); + } + return getMasterKeyId(); + } + + public String getPrimaryUserId() throws PgpGeneralException { + try { + Object data = mProviderHelper.getGenericData(mUri, + KeychainContract.KeyRings.MASTER_KEY_ID, + ProviderHelper.FIELD_TYPE_STRING); + return (String) data; + } catch(ProviderHelper.NotFoundException e) { + throw new PgpGeneralException(e); + } + } + + public boolean isRevoked() throws PgpGeneralException { + try { + Object data = mProviderHelper.getGenericData(mUri, + KeychainContract.KeyRings.MASTER_KEY_ID, + ProviderHelper.FIELD_TYPE_INTEGER); + return (Long) data > 0; + } catch(ProviderHelper.NotFoundException e) { + throw new PgpGeneralException(e); + } + } + + public boolean canCertify() throws PgpGeneralException { + try { + Object data = mProviderHelper.getGenericData(mUri, + KeychainContract.KeyRings.MASTER_KEY_ID, + ProviderHelper.FIELD_TYPE_INTEGER); + return (Long) data > 0; + } catch(ProviderHelper.NotFoundException e) { + throw new PgpGeneralException(e); + } + } + + public long getEncryptId() throws PgpGeneralException { + try { + Object data = mProviderHelper.getGenericData(mUri, + KeychainContract.KeyRings.MASTER_KEY_ID, + ProviderHelper.FIELD_TYPE_INTEGER); + return (Long) data; + } catch(ProviderHelper.NotFoundException e) { + throw new PgpGeneralException(e); + } + } + + public boolean hasEncrypt() throws PgpGeneralException { + try { + Object data = mProviderHelper.getGenericData(mUri, + KeychainContract.KeyRings.MASTER_KEY_ID, + ProviderHelper.FIELD_TYPE_INTEGER); + return (Long) data > 0; + } catch(ProviderHelper.NotFoundException e) { + throw new PgpGeneralException(e); + } + } + + public long getSignId() throws PgpGeneralException { + try { + Object data = mProviderHelper.getGenericData(mUri, + KeychainContract.KeyRings.MASTER_KEY_ID, + ProviderHelper.FIELD_TYPE_INTEGER); + return (Long) data; + } catch(ProviderHelper.NotFoundException e) { + throw new PgpGeneralException(e); + } + } + + public boolean hasSign() throws PgpGeneralException { + try { + Object data = mProviderHelper.getGenericData(mUri, + KeychainContract.KeyRings.MASTER_KEY_ID, + ProviderHelper.FIELD_TYPE_INTEGER); + return (Long) data > 0; + } catch(ProviderHelper.NotFoundException e) { + throw new PgpGeneralException(e); + } + } + + public int getVerified() throws PgpGeneralException { + try { + Object data = mProviderHelper.getGenericData(mUri, + KeychainContract.KeyRings.MASTER_KEY_ID, + ProviderHelper.FIELD_TYPE_INTEGER); + return (Integer) data; + } catch(ProviderHelper.NotFoundException e) { + throw new PgpGeneralException(e); + } + } + + public boolean hasAnySecret() throws PgpGeneralException { + try { + Object data = mProviderHelper.getGenericData(mUri, + KeychainContract.KeyRings.MASTER_KEY_ID, + ProviderHelper.FIELD_TYPE_INTEGER); + return (Long) data > 0; + } catch(ProviderHelper.NotFoundException e) { + throw new PgpGeneralException(e); + } + + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index a4fa3dac9..483f762f7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -110,6 +110,8 @@ public class KeychainContract { public static final String HAS_ANY_SECRET = "has_any_secret"; public static final String HAS_ENCRYPT = "has_encrypt"; public static final String HAS_SIGN = "has_sign"; + public static final String PUBKEY_DATA = "pubkey_data"; + public static final String PRIVKEY_DATA = "privkey_data"; public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() .appendPath(BASE_KEY_RINGS).build(); @@ -123,6 +125,10 @@ public class KeychainContract { return CONTENT_URI.buildUpon().appendPath(PATH_UNIFIED).build(); } + public static Uri buildGenericKeyRingUri(long masterKeyId) { + return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)).build(); + } + public static Uri buildGenericKeyRingUri(String masterKeyId) { return CONTENT_URI.buildUpon().appendPath(masterKeyId).build(); } @@ -131,20 +137,24 @@ public class KeychainContract { return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).build(); } - public static Uri buildUnifiedKeyRingUri(String masterKeyId) { - return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_UNIFIED).build(); + public static Uri buildUnifiedKeyRingUri(long masterKeyId) { + return CONTENT_URI.buildUpon().appendPath(Long.toString(masterKeyId)) + .appendPath(PATH_UNIFIED).build(); } public static Uri buildUnifiedKeyRingUri(Uri uri) { - return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_UNIFIED).build(); + return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)) + .appendPath(PATH_UNIFIED).build(); } public static Uri buildUnifiedKeyRingsFindByEmailUri(String email) { - return CONTENT_URI.buildUpon().appendPath(PATH_FIND).appendPath(PATH_BY_EMAIL).appendPath(email).build(); + return CONTENT_URI.buildUpon().appendPath(PATH_FIND) + .appendPath(PATH_BY_EMAIL).appendPath(email).build(); } - public static Uri buildUnifiedKeyRingsFindBySubkeyUri(String subkey) { - return CONTENT_URI.buildUpon().appendPath(PATH_FIND).appendPath(PATH_BY_SUBKEY).appendPath(subkey).build(); + public static Uri buildUnifiedKeyRingsFindBySubkeyUri(long subkey) { + return CONTENT_URI.buildUpon().appendPath(PATH_FIND) + .appendPath(PATH_BY_SUBKEY).appendPath(Long.toString(subkey)).build(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index 68726d3e0..ceaa93f9b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -23,11 +23,9 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.provider.BaseColumns; -import org.spongycastle.openpgp.PGPKeyRing; -import org.spongycastle.openpgp.PGPPublicKeyRing; -import org.spongycastle.openpgp.PGPSecretKeyRing; import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.pgp.PgpConversionHelper; +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsAccountsColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.CertsColumns; @@ -256,6 +254,8 @@ public class KeychainDatabase extends SQLiteOpenHelper { }.getReadableDatabase(); Cursor cursor = null; + ProviderHelper providerHelper = new ProviderHelper(context); + try { // we insert in two steps: first, all public keys that have secret keys cursor = db.rawQuery("SELECT key_ring_data FROM key_rings WHERE type = 1 OR EXISTS (" @@ -266,14 +266,11 @@ public class KeychainDatabase extends SQLiteOpenHelper { for (int i = 0; i < cursor.getCount(); i++) { cursor.moveToPosition(i); byte[] data = cursor.getBlob(0); - PGPKeyRing ring = PgpConversionHelper.BytesToPGPKeyRing(data); - ProviderHelper providerHelper = new ProviderHelper(context); - if (ring instanceof PGPPublicKeyRing) - providerHelper.saveKeyRing((PGPPublicKeyRing) ring); - else if (ring instanceof PGPSecretKeyRing) - providerHelper.saveKeyRing((PGPSecretKeyRing) ring); - else { - Log.e(Constants.TAG, "Unknown blob data type!"); + try { + UncachedKeyRing ring = UncachedKeyRing.decodeFromData(data); + providerHelper.savePublicKeyRing(ring); + } catch(PgpGeneralException e) { + Log.e(Constants.TAG, "Error decoding keyring blob!"); } } } @@ -293,14 +290,11 @@ public class KeychainDatabase extends SQLiteOpenHelper { for (int i = 0; i < cursor.getCount(); i++) { cursor.moveToPosition(i); byte[] data = cursor.getBlob(0); - PGPKeyRing ring = PgpConversionHelper.BytesToPGPKeyRing(data); - ProviderHelper providerHelper = new ProviderHelper(context); - if (ring instanceof PGPPublicKeyRing) { - providerHelper.saveKeyRing((PGPPublicKeyRing) ring); - } else if (ring instanceof PGPSecretKeyRing) { - providerHelper.saveKeyRing((PGPSecretKeyRing) ring); - } else { - Log.e(Constants.TAG, "Unknown blob data type!"); + try { + UncachedKeyRing ring = UncachedKeyRing.decodeFromData(data); + providerHelper.savePublicKeyRing(ring); + } catch(PgpGeneralException e) { + Log.e(Constants.TAG, "Error decoding keyring blob!"); } } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index ec7bf58d9..b651069e9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -43,6 +43,7 @@ import org.sufficientlysecure.keychain.util.Log; import java.util.Arrays; import java.util.Date; import java.util.HashMap; +import java.util.List; public class KeychainProvider extends ContentProvider { @@ -242,45 +243,39 @@ public class KeychainProvider extends ContentProvider { HashMap<String, String> projectionMap = new HashMap<String, String>(); projectionMap.put(KeyRings._ID, Tables.KEYS + ".oid AS _id"); 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.KEY_ID, Tables.KEYS + "." + Keys.KEY_ID); + projectionMap.put(KeyRings.KEY_SIZE, Tables.KEYS + "." + Keys.KEY_SIZE); 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); + projectionMap.put(KeyRings.CAN_CERTIFY, Tables.KEYS + "." + Keys.CAN_CERTIFY); + projectionMap.put(KeyRings.CAN_ENCRYPT, Tables.KEYS + "." + Keys.CAN_ENCRYPT); + projectionMap.put(KeyRings.CAN_SIGN, Tables.KEYS + "." + Keys.CAN_SIGN); projectionMap.put(KeyRings.CREATION, Tables.KEYS + "." + Keys.CREATION); - projectionMap.put(KeyRings.EXPIRY, Keys.EXPIRY); - projectionMap.put(KeyRings.ALGORITHM, Keys.ALGORITHM); - projectionMap.put(KeyRings.FINGERPRINT, Keys.FINGERPRINT); + projectionMap.put(KeyRings.EXPIRY, Tables.KEYS + "." + Keys.EXPIRY); + projectionMap.put(KeyRings.ALGORITHM, Tables.KEYS + "." + Keys.ALGORITHM); + projectionMap.put(KeyRings.FINGERPRINT, Tables.KEYS + "." + Keys.FINGERPRINT); projectionMap.put(KeyRings.USER_ID, UserIds.USER_ID); projectionMap.put(KeyRings.VERIFIED, KeyRings.VERIFIED); - projectionMap.put(KeyRings.HAS_SECRET, KeyRings.HAS_SECRET); + projectionMap.put(KeyRings.PUBKEY_DATA, + Tables.KEY_RINGS_PUBLIC + "." + KeyRingData.KEY_RING_DATA + + " AS " + KeyRings.PUBKEY_DATA); + projectionMap.put(KeyRings.PRIVKEY_DATA, + Tables.KEY_RINGS_SECRET + "." + KeyRingData.KEY_RING_DATA + + " AS " + KeyRings.PRIVKEY_DATA); + projectionMap.put(KeyRings.HAS_SECRET, Tables.KEYS + "." + KeyRings.HAS_SECRET); projectionMap.put(KeyRings.HAS_ANY_SECRET, "(EXISTS (SELECT * FROM " + Tables.KEY_RINGS_SECRET + " WHERE " + Tables.KEY_RINGS_SECRET + "." + KeyRingData.MASTER_KEY_ID + " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID + ")) AS " + KeyRings.HAS_ANY_SECRET); projectionMap.put(KeyRings.HAS_ENCRYPT, - "(EXISTS (SELECT * FROM " + Tables.KEYS + " AS k" - +" WHERE k." + Keys.MASTER_KEY_ID - + " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID - + " AND k." + Keys.IS_REVOKED + " = 0" - + " AND k." + Keys.CAN_ENCRYPT + " = 1" - + " AND ( k." + Keys.EXPIRY + " IS NULL OR k." + Keys.EXPIRY - + " >= " + new Date().getTime() / 1000 + " )" - + ")) AS " + KeyRings.HAS_ENCRYPT); + "kE." + Keys.KEY_ID + " AS " + KeyRings.HAS_ENCRYPT); projectionMap.put(KeyRings.HAS_SIGN, - "(EXISTS (SELECT * FROM " + Tables.KEYS + " AS k" - +" WHERE k." + Keys.MASTER_KEY_ID - + " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID - + " AND k." + Keys.IS_REVOKED + " = 0" - + " AND k." + Keys.HAS_SECRET + " = 1" - + " AND k." + Keys.CAN_SIGN + " = 1" - + " AND ( k." + Keys.EXPIRY + " IS NULL OR k." + Keys.EXPIRY - + " >= " + new Date().getTime() / 1000 + " )" - + ")) AS " + KeyRings.HAS_SIGN); + "kS." + Keys.KEY_ID + " AS " + KeyRings.HAS_SIGN); qb.setProjectionMap(projectionMap); + // Need this as list so we can search in it + List<String> plist = Arrays.asList(projection); + qb.setTables( Tables.KEYS + " INNER JOIN " + Tables.USER_IDS + " ON (" @@ -295,6 +290,37 @@ public class KeychainProvider extends ContentProvider { + " AND " + Tables.CERTS + "." + Certs.VERIFIED + " = " + Certs.VERIFIED_SECRET + ")" + // fairly expensive joins following, only do when requested + + (plist.contains(KeyRings.PUBKEY_DATA) ? + " INNER JOIN " + Tables.KEY_RINGS_PUBLIC + " ON (" + + Tables.KEYS + "." + Keys.MASTER_KEY_ID + + " = " + + Tables.KEY_RINGS_PUBLIC + "." + KeyRingData.MASTER_KEY_ID + + ")" : "") + + (plist.contains(KeyRings.PRIVKEY_DATA) ? + " LEFT JOIN " + Tables.KEY_RINGS_SECRET + " ON (" + + Tables.KEYS + "." + Keys.MASTER_KEY_ID + + " = " + + Tables.KEY_RINGS_SECRET + "." + KeyRingData.MASTER_KEY_ID + + ")" : "") + + (plist.contains(KeyRings.HAS_ENCRYPT) ? + " LEFT JOIN " + Tables.KEYS + " AS kE ON (" + +"kE." + Keys.MASTER_KEY_ID + + " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID + + " AND kE." + Keys.IS_REVOKED + " = 0" + + " AND kE." + Keys.CAN_ENCRYPT + " = 1" + + " AND ( kE." + Keys.EXPIRY + " IS NULL OR kE." + Keys.EXPIRY + + " >= " + new Date().getTime() / 1000 + " )" + + ")" : "") + + (plist.contains(KeyRings.HAS_SIGN) ? + " LEFT JOIN " + Tables.KEYS + " AS kS ON (" + +"kS." + Keys.MASTER_KEY_ID + + " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID + + " AND kS." + Keys.IS_REVOKED + " = 0" + + " AND kS." + Keys.CAN_SIGN + " = 1" + + " AND ( kS." + Keys.EXPIRY + " IS NULL OR kS." + Keys.EXPIRY + + " >= " + new Date().getTime() / 1000 + " )" + + ")" : "") ); qb.appendWhere(Tables.KEYS + "." + Keys.RANK + " = 0"); // in case there are multiple verifying certificates @@ -595,7 +621,7 @@ public class KeychainProvider extends ContentProvider { case KEY_RING_CERTS: // we replace here, keeping only the latest signature - // TODO this would be better handled in saveKeyRing directly! + // TODO this would be better handled in savePublicKeyRing directly! db.replaceOrThrow(Tables.CERTS, null, values); keyId = values.getAsLong(Certs.MASTER_KEY_ID); break; @@ -618,7 +644,7 @@ public class KeychainProvider extends ContentProvider { } if(keyId != null) { - uri = KeyRings.buildGenericKeyRingUri(keyId.toString()); + uri = KeyRings.buildGenericKeyRingUri(keyId); rowUri = uri; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java index ab00db13a..043c40b25 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -23,26 +23,20 @@ import android.content.ContentValues; import android.content.Context; import android.content.OperationApplicationException; import android.database.Cursor; -import android.database.DatabaseUtils; import android.net.Uri; import android.os.RemoteException; import android.support.v4.util.LongSparseArray; -import org.spongycastle.bcpg.ArmoredOutputStream; -import org.spongycastle.bcpg.S2K; -import org.spongycastle.openpgp.PGPException; -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.spongycastle.openpgp.operator.PBESecretKeyDecryptor; -import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.pgp.PgpConversionHelper; +import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.pgp.UncachedPublicKey; +import org.sufficientlysecure.keychain.pgp.WrappedSecretKeyRing; +import org.sufficientlysecure.keychain.pgp.WrappedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; +import org.sufficientlysecure.keychain.pgp.WrappedSignature; +import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; @@ -56,14 +50,12 @@ import org.sufficientlysecure.keychain.util.Log; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.security.SignatureException; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; public class ProviderHelper { @@ -141,47 +133,29 @@ public class ProviderHelper { public HashMap<String, Object> getUnifiedData(long masterKeyId, String[] proj, int[] types) throws NotFoundException { - return getGenericData(KeyRings.buildUnifiedKeyRingUri(Long.toString(masterKeyId)), proj, types); + return getGenericData(KeyRings.buildUnifiedKeyRingUri(masterKeyId), proj, types); } - /** - * Find the master key id related to a given query. The id will either be extracted from the - * query, which should work for all specific /key_rings/ queries, or will be queried if it can't. - */ - public long extractOrGetMasterKeyId(Uri queryUri) - throws NotFoundException { - // try extracting from the uri first - String firstSegment = queryUri.getPathSegments().get(1); - if (!firstSegment.equals("find")) try { - return Long.parseLong(firstSegment); - } catch (NumberFormatException e) { - // didn't work? oh well. - Log.d(Constants.TAG, "Couldn't get masterKeyId from URI, querying..."); - } - return getMasterKeyId(queryUri); - } - - public long getMasterKeyId(Uri queryUri) throws NotFoundException { - Object data = getGenericData(queryUri, KeyRings.MASTER_KEY_ID, FIELD_TYPE_INTEGER); - if (data != null) { - return (Long) data; - } else { - throw new NotFoundException(); - } - } - - public LongSparseArray<PGPKeyRing> getPGPKeyRings(Uri queryUri) { + private LongSparseArray<UncachedPublicKey> getUncachedMasterKeys(Uri queryUri) { Cursor cursor = mContentResolver.query(queryUri, new String[]{KeyRingData.MASTER_KEY_ID, KeyRingData.KEY_RING_DATA}, null, null, null); - LongSparseArray<PGPKeyRing> result = new LongSparseArray<PGPKeyRing>(cursor.getCount()); + LongSparseArray<UncachedPublicKey> result = + new LongSparseArray<UncachedPublicKey>(cursor.getCount()); try { if (cursor != null && cursor.moveToFirst()) do { long masterKeyId = cursor.getLong(0); byte[] data = cursor.getBlob(1); if (data != null) { - result.put(masterKeyId, PgpConversionHelper.BytesToPGPKeyRing(data)); + try { + result.put(masterKeyId, + UncachedKeyRing.decodePublicFromData(data).getPublicKey()); + } catch(PgpGeneralException e) { + Log.e(Constants.TAG, "Error parsing keyring, skipping."); + } catch(IOException e) { + Log.e(Constants.TAG, "IO error, skipping keyring"); + } } } while (cursor.moveToNext()); } finally { @@ -193,57 +167,74 @@ public class ProviderHelper { return result; } - public PGPKeyRing getPGPKeyRing(Uri queryUri) throws NotFoundException { - LongSparseArray<PGPKeyRing> result = getPGPKeyRings(queryUri); - if (result.size() == 0) { - throw new NotFoundException("PGPKeyRing object not found!"); - } else { - return result.valueAt(0); - } + public CachedPublicKeyRing getCachedPublicKeyRing(Uri queryUri) { + return new CachedPublicKeyRing(this, queryUri); } - public PGPPublicKeyRing getPGPPublicKeyRingWithKeyId(long keyId) - throws NotFoundException { - Uri uri = KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(Long.toString(keyId)); - long masterKeyId = getMasterKeyId(uri); - return getPGPPublicKeyRing(masterKeyId); + public WrappedPublicKeyRing getWrappedPublicKeyRing(long id) throws NotFoundException { + return (WrappedPublicKeyRing) getWrappedKeyRing(KeyRings.buildUnifiedKeyRingUri(id), false); } - public PGPSecretKeyRing getPGPSecretKeyRingWithKeyId(long keyId) - throws NotFoundException { - Uri uri = KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(Long.toString(keyId)); - long masterKeyId = getMasterKeyId(uri); - return getPGPSecretKeyRing(masterKeyId); + public WrappedPublicKeyRing getWrappedPublicKeyRing(Uri queryUri) throws NotFoundException { + return (WrappedPublicKeyRing) getWrappedKeyRing(queryUri, false); } - /** - * Retrieves the actual PGPPublicKeyRing object from the database blob based on the masterKeyId - */ - public PGPPublicKeyRing getPGPPublicKeyRing(long masterKeyId) throws NotFoundException { - Uri queryUri = KeyRingData.buildPublicKeyRingUri(Long.toString(masterKeyId)); - return (PGPPublicKeyRing) getPGPKeyRing(queryUri); + public WrappedSecretKeyRing getWrappedSecretKeyRing(long id) throws NotFoundException { + return (WrappedSecretKeyRing) getWrappedKeyRing(KeyRings.buildUnifiedKeyRingUri(id), true); } - /** - * Retrieves the actual PGPSecretKeyRing object from the database blob based on the maserKeyId - */ - public PGPSecretKeyRing getPGPSecretKeyRing(long masterKeyId) throws NotFoundException { - Uri queryUri = KeyRingData.buildSecretKeyRingUri(Long.toString(masterKeyId)); - return (PGPSecretKeyRing) getPGPKeyRing(queryUri); + public WrappedSecretKeyRing getWrappedSecretKeyRing(Uri queryUri) throws NotFoundException { + return (WrappedSecretKeyRing) getWrappedKeyRing(queryUri, true); + } + + private KeyRing getWrappedKeyRing(Uri queryUri, boolean secret) throws NotFoundException { + Cursor cursor = mContentResolver.query(queryUri, + new String[]{ + // we pick from cache only information that is not easily available from keyrings + KeyRings.HAS_ANY_SECRET, KeyRings.VERIFIED, + // and of course, ring data + secret ? KeyRings.PRIVKEY_DATA : KeyRings.PUBKEY_DATA + }, null, null, null + ); + try { + if (cursor != null && cursor.moveToFirst()) { + + boolean hasAnySecret = cursor.getInt(0) > 0; + int verified = cursor.getInt(1); + byte[] blob = cursor.getBlob(2); + if(secret &! hasAnySecret) { + throw new NotFoundException("Secret key not available!"); + } + return secret + ? new WrappedSecretKeyRing(blob, hasAnySecret, verified) + : new WrappedPublicKeyRing(blob, hasAnySecret, verified); + } else { + throw new NotFoundException("Key not found!"); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } } /** * Saves PGPPublicKeyRing with its keys and userIds in DB */ @SuppressWarnings("unchecked") - public void saveKeyRing(PGPPublicKeyRing keyRing) throws IOException { - PGPPublicKey masterKey = keyRing.getPublicKey(); - long masterKeyId = masterKey.getKeyID(); + public void savePublicKeyRing(UncachedKeyRing keyRing) throws IOException { + if (keyRing.isSecret()) { + throw new RuntimeException("Tried to save secret keyring as public! " + + "This is a bug, please file a bug report."); + } + + UncachedPublicKey masterKey = keyRing.getPublicKey(); + long masterKeyId = masterKey.getKeyId(); // IF there is a secret key, preserve it! - PGPSecretKeyRing secretRing = null; + UncachedKeyRing secretRing = null; try { - secretRing = getPGPSecretKeyRing(masterKeyId); + secretRing = getWrappedSecretKeyRing(masterKeyId).getUncached(); } catch (NotFoundException e) { Log.e(Constants.TAG, "key not found!"); } @@ -266,36 +257,38 @@ public class ProviderHelper { ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>(); int rank = 0; - for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(keyRing.getPublicKeys())) { + for (UncachedPublicKey key : new IterableIterator<UncachedPublicKey>(keyRing.getPublicKeys())) { operations.add(buildPublicKeyOperations(masterKeyId, key, rank)); ++rank; } // get a list of owned secret keys, for verification filtering - LongSparseArray<PGPKeyRing> allKeyRings = getPGPKeyRings(KeyRingData.buildSecretKeyRingUri()); + LongSparseArray<UncachedPublicKey> allKeyRings = + getUncachedMasterKeys(KeyRingData.buildSecretKeyRingUri()); // special case: available secret keys verify themselves! - if (secretRing != null) - allKeyRings.put(secretRing.getSecretKey().getKeyID(), secretRing); + if (secretRing != null) { + allKeyRings.put(secretRing.getMasterKeyId(), secretRing.getPublicKey()); + } // 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())) { + for (String userId : new IterableIterator<String>( + masterKey.getUnorderedUserIds().iterator())) { 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(); + for (WrappedSignature cert : new IterableIterator<WrappedSignature>( + 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)) { + cert.init(masterKey); + if (!cert.verifySignature(masterKey, userId)) { // not verified?! dang! TODO notify user? this is kinda serious... Log.e(Constants.TAG, "Could not verify self signature for " + userId + "!"); continue; @@ -304,31 +297,22 @@ public class ProviderHelper { 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; + item.isPrimary = cert.isPrimaryUserId(); + item.isRevoked = cert.isRevocation(); } } // verify signatures from known private keys if (allKeyRings.indexOfKey(certId) >= 0) { - // mark them as verified - cert.init(new JcaPGPContentVerifierBuilderProvider().setProvider( - Constants.BOUNCY_CASTLE_PROVIDER_NAME), - allKeyRings.get(certId).getPublicKey()); - if (cert.verifyCertification(userId, masterKey)) { + cert.init(allKeyRings.get(certId)); + if (cert.verifySignature(masterKey, userId)) { item.trustedCerts.add(cert); } } - } catch (SignatureException e) { + } catch (PgpGeneralException e) { Log.e(Constants.TAG, "Signature verification failed! " - + PgpKeyHelper.convertKeyIdToHex(masterKey.getKeyID()) + + 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); + + PgpKeyHelper.convertKeyIdToHex(cert.getKeyId()), e); } } } @@ -365,7 +349,7 @@ public class ProviderHelper { // Save the saved keyring (if any) if (secretRing != null) { - saveKeyRing(secretRing); + saveSecretKeyRing(secretRing); } } @@ -374,8 +358,8 @@ public class ProviderHelper { String userId; boolean isPrimary = false; boolean isRevoked = false; - PGPSignature selfCert; - List<PGPSignature> trustedCerts = new ArrayList<PGPSignature>(); + WrappedSignature selfCert; + List<WrappedSignature> trustedCerts = new ArrayList<WrappedSignature>(); @Override public int compareTo(UserIdItem o) { @@ -395,8 +379,13 @@ public class ProviderHelper { * Saves a PGPSecretKeyRing in the DB. This will only work if a corresponding public keyring * is already in the database! */ - public void saveKeyRing(PGPSecretKeyRing keyRing) throws IOException { - long masterKeyId = keyRing.getPublicKey().getKeyID(); + public void saveSecretKeyRing(UncachedKeyRing keyRing) throws IOException { + if (!keyRing.isSecret()) { + throw new RuntimeException("Tried to save publkc keyring as secret! " + + "This is a bug, please file a bug report."); + } + + long masterKeyId = keyRing.getMasterKeyId(); { Uri uri = Keys.buildKeysUri(Long.toString(masterKeyId)); @@ -408,14 +397,10 @@ public class ProviderHelper { values.put(Keys.HAS_SECRET, 1); // then, mark exactly the keys we have available - for (PGPSecretKey sub : new IterableIterator<PGPSecretKey>(keyRing.getSecretKeys())) { - S2K s2k = sub.getS2K(); - // Set to 1, except if the encryption type is GNU_DUMMY_S2K - if(s2k == null || s2k.getType() != S2K.GNU_DUMMY_S2K) { - mContentResolver.update(uri, values, Keys.KEY_ID + " = ?", new String[]{ - Long.toString(sub.getKeyID()) - }); - } + for (Long sub : new IterableIterator<Long>(keyRing.getAvailableSubkeys().iterator())) { + mContentResolver.update(uri, values, Keys.KEY_ID + " = ?", new String[] { + Long.toString(sub) + }); } // this implicitly leaves all keys which were not in the secret key ring // with has_secret = 0 @@ -436,39 +421,39 @@ public class ProviderHelper { /** * Saves (or updates) a pair of public and secret KeyRings in the database */ - public void saveKeyRing(PGPPublicKeyRing pubRing, PGPSecretKeyRing privRing) throws IOException { - long masterKeyId = pubRing.getPublicKey().getKeyID(); + public void saveKeyRing(UncachedKeyRing pubRing, UncachedKeyRing secRing) throws IOException { + long masterKeyId = pubRing.getPublicKey().getKeyId(); - // delete secret keyring (so it isn't unnecessarily saved by public-saveKeyRing below) + // delete secret keyring (so it isn't unnecessarily saved by public-savePublicKeyRing below) mContentResolver.delete(KeyRingData.buildSecretKeyRingUri(Long.toString(masterKeyId)), null, null); // save public keyring - saveKeyRing(pubRing); - saveKeyRing(privRing); + savePublicKeyRing(pubRing); + saveSecretKeyRing(secRing); } /** * Build ContentProviderOperation to add PGPPublicKey to database corresponding to a keyRing */ private ContentProviderOperation - buildPublicKeyOperations(long masterKeyId, PGPPublicKey key, int rank) throws IOException { + buildPublicKeyOperations(long masterKeyId, UncachedPublicKey key, int rank) throws IOException { ContentValues values = new ContentValues(); values.put(Keys.MASTER_KEY_ID, masterKeyId); values.put(Keys.RANK, rank); - values.put(Keys.KEY_ID, key.getKeyID()); + values.put(Keys.KEY_ID, key.getKeyId()); values.put(Keys.KEY_SIZE, key.getBitStrength()); values.put(Keys.ALGORITHM, key.getAlgorithm()); values.put(Keys.FINGERPRINT, key.getFingerprint()); - values.put(Keys.CAN_CERTIFY, (PgpKeyHelper.isCertificationKey(key))); - values.put(Keys.CAN_SIGN, (PgpKeyHelper.isSigningKey(key))); - values.put(Keys.CAN_ENCRYPT, PgpKeyHelper.isEncryptionKey(key)); - values.put(Keys.IS_REVOKED, key.isRevoked()); + values.put(Keys.CAN_CERTIFY, key.canCertify()); + values.put(Keys.CAN_SIGN, key.canSign()); + values.put(Keys.CAN_ENCRYPT, key.canEncrypt()); + values.put(Keys.IS_REVOKED, key.maybeRevoked()); - values.put(Keys.CREATION, PgpKeyHelper.getCreationDate(key).getTime() / 1000); - Date expiryDate = PgpKeyHelper.getExpiryDate(key); + values.put(Keys.CREATION, key.getCreationTime().getTime() / 1000); + Date expiryDate = key.getExpiryTime(); if (expiryDate != null) { values.put(Keys.EXPIRY, expiryDate.getTime() / 1000); } @@ -482,11 +467,12 @@ public class ProviderHelper { * Build ContentProviderOperation to add PGPPublicKey to database corresponding to a keyRing */ private ContentProviderOperation - buildCertOperations(long masterKeyId, int rank, PGPSignature cert, int verified) throws IOException { + buildCertOperations(long masterKeyId, int rank, WrappedSignature 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.KEY_ID_CERTIFIER, cert.getKeyId()); values.put(Certs.TYPE, cert.getSignatureType()); values.put(Certs.CREATION, cert.getCreationTime().getTime() / 1000); values.put(Certs.VERIFIED, verified); @@ -514,23 +500,11 @@ public class ProviderHelper { return ContentProviderOperation.newInsert(uri).withValues(values).build(); } - private String getKeyRingAsArmoredString(byte[] data) throws IOException { - Object keyRing = null; - if (data != null) { - keyRing = PgpConversionHelper.BytesToPGPKeyRing(data); - } + private String getKeyRingAsArmoredString(byte[] data) throws IOException, PgpGeneralException { + UncachedKeyRing keyRing = UncachedKeyRing.decodeFromData(data); ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ArmoredOutputStream aos = new ArmoredOutputStream(bos); - aos.setHeader("Version", PgpHelper.getFullVersion(mContext)); - - if (keyRing instanceof PGPSecretKeyRing) { - aos.write(((PGPSecretKeyRing) keyRing).getEncoded()); - } else if (keyRing instanceof PGPPublicKeyRing) { - aos.write(((PGPPublicKeyRing) keyRing).getEncoded()); - } - aos.close(); - + keyRing.encodeArmored(bos, PgpHelper.getFullVersion(mContext)); String armoredKey = bos.toString("UTF-8"); Log.d(Constants.TAG, "armoredKey:" + armoredKey); @@ -539,77 +513,12 @@ public class ProviderHelper { } public String getKeyRingAsArmoredString(Uri uri) - throws NotFoundException, IOException { + throws NotFoundException, IOException, PgpGeneralException { byte[] data = (byte[]) getGenericData( uri, KeyRingData.KEY_RING_DATA, ProviderHelper.FIELD_TYPE_BLOB); return getKeyRingAsArmoredString(data); } - /** - * TODO: currently not used, but will be needed to upload many keys at once! - * - * @param masterKeyIds - * @return - * @throws IOException - */ - public ArrayList<String> getKeyRingsAsArmoredString(long[] masterKeyIds) - throws IOException { - ArrayList<String> output = new ArrayList<String>(); - - if (masterKeyIds == null || masterKeyIds.length == 0) { - Log.e(Constants.TAG, "No master keys given!"); - return output; - } - - // Build a cursor for the selected masterKeyIds - Cursor cursor; - { - String inMasterKeyList = KeyRingData.MASTER_KEY_ID + " IN ("; - for (int i = 0; i < masterKeyIds.length; ++i) { - if (i != 0) { - inMasterKeyList += ", "; - } - inMasterKeyList += DatabaseUtils.sqlEscapeString("" + masterKeyIds[i]); - } - inMasterKeyList += ")"; - - cursor = mContentResolver.query(KeyRingData.buildPublicKeyRingUri(), new String[]{ - KeyRingData._ID, KeyRingData.MASTER_KEY_ID, KeyRingData.KEY_RING_DATA - }, inMasterKeyList, null, null); - } - - try { - if (cursor != null) { - int masterIdCol = cursor.getColumnIndex(KeyRingData.MASTER_KEY_ID); - int dataCol = cursor.getColumnIndex(KeyRingData.KEY_RING_DATA); - if (cursor.moveToFirst()) { - do { - Log.d(Constants.TAG, "masterKeyId: " + cursor.getLong(masterIdCol)); - - byte[] data = cursor.getBlob(dataCol); - - // get actual keyring data blob and write it to ByteArrayOutputStream - try { - output.add(getKeyRingAsArmoredString(data)); - } catch (IOException e) { - Log.e(Constants.TAG, "IOException", e); - } - } while (cursor.moveToNext()); - } - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - - if (output.size() > 0) { - return output; - } else { - return null; - } - } - public ArrayList<String> getRegisteredApiApps() { Cursor cursor = mContentResolver.query(ApiApps.CONTENT_URI, null, null, null, null); |