diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainExternalContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainExternalContract.java index f4d9d657b..d09046831 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainExternalContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainExternalContract.java @@ -22,6 +22,7 @@ import android.net.Uri; import android.provider.BaseColumns; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.provider.KeychainContract.ApiTrustIdentityColumns; public class KeychainExternalContract { @@ -31,26 +32,39 @@ public class KeychainExternalContract { // this is in KeychainExternalContract already, but we want to be double // sure this isn't mixed up with the internal one! public static final String CONTENT_AUTHORITY_EXTERNAL = Constants.PROVIDER_AUTHORITY + ".exported"; - private static final Uri BASE_CONTENT_URI_EXTERNAL = Uri .parse("content://" + CONTENT_AUTHORITY_EXTERNAL); - public static final String BASE_EMAIL_STATUS = "email_status"; + public static final String BASE_TRUST_IDENTITIES = "trust_ids"; + public static class EmailStatus implements BaseColumns { public static final String EMAIL_ADDRESS = "email_address"; public static final String USER_ID = "user_id"; public static final String USER_ID_STATUS = "email_status"; public static final String MASTER_KEY_ID = "master_key_id"; + public static final String TRUST_ID_LAST_UPDATE = "trust_id_last_update"; public static final Uri CONTENT_URI = BASE_CONTENT_URI_EXTERNAL.buildUpon() .appendPath(BASE_EMAIL_STATUS).build(); - public static final String CONTENT_TYPE - = "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.email_status"; + public static final String CONTENT_TYPE = + "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.email_status"; + } + + public static class ApiTrustIdentity implements ApiTrustIdentityColumns, BaseColumns { + public static final Uri CONTENT_URI = BASE_CONTENT_URI_EXTERNAL.buildUpon() + .appendPath(BASE_TRUST_IDENTITIES).build(); + + public static Uri buildByPackageNameUri(String packageName) { + return CONTENT_URI.buildUpon().appendEncodedPath(packageName).build(); + } + + public static Uri buildByPackageNameAndTrustIdUri(String packageName, String trustId) { + return CONTENT_URI.buildUpon().appendEncodedPath(packageName).appendEncodedPath(trustId).build(); + } } private KeychainExternalContract() { } - } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java index 29d874f9a..82fa1a4f2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java @@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.remote; import java.security.AccessControlException; import java.util.Arrays; +import java.util.Date; import java.util.HashMap; import java.util.List; @@ -45,6 +46,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import org.sufficientlysecure.keychain.provider.KeychainDatabase; import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.provider.KeychainExternalContract; +import org.sufficientlysecure.keychain.provider.KeychainExternalContract.ApiTrustIdentity; import org.sufficientlysecure.keychain.provider.KeychainExternalContract.EmailStatus; import org.sufficientlysecure.keychain.provider.SimpleContentResolverInterface; import org.sufficientlysecure.keychain.util.Log; @@ -52,6 +54,7 @@ import org.sufficientlysecure.keychain.util.Log; public class KeychainExternalProvider extends ContentProvider implements SimpleContentResolverInterface { private static final int EMAIL_STATUS = 101; private static final int EMAIL_STATUS_INTERNAL = 102; + private static final int TRUST_IDENTITY = 201; private static final int API_APPS = 301; private static final int API_APPS_BY_PACKAGE_NAME = 302; @@ -82,6 +85,8 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC matcher.addURI(authority, KeychainExternalContract.BASE_EMAIL_STATUS, EMAIL_STATUS); matcher.addURI(authority, KeychainExternalContract.BASE_EMAIL_STATUS + "/*", EMAIL_STATUS_INTERNAL); + matcher.addURI(authority, KeychainExternalContract.BASE_TRUST_IDENTITIES + "/*", TRUST_IDENTITY); + // can only query status of calling app - for internal use only! matcher.addURI(KeychainContract.CONTENT_AUTHORITY, KeychainContract.BASE_API_APPS + "/*", API_APPS_BY_PACKAGE_NAME); @@ -174,9 +179,12 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC + " WHEN " + Certs.VERIFIED_SECRET + " THEN " + KeychainExternalContract.KEY_STATUS_VERIFIED + " WHEN NULL THEN " + KeychainExternalContract.KEY_STATUS_UNVERIFIED + " END AS " + EmailStatus.USER_ID_STATUS); - projectionMap.put(EmailStatus.USER_ID, Tables.USER_PACKETS + "." + UserPackets.USER_ID + " AS " + EmailStatus.USER_ID); projectionMap.put(EmailStatus.MASTER_KEY_ID, Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " AS " + EmailStatus.MASTER_KEY_ID); + projectionMap.put(EmailStatus.USER_ID, + Tables.USER_PACKETS + "." + UserPackets.USER_ID + " AS " + EmailStatus.USER_ID); + projectionMap.put(EmailStatus.TRUST_ID_LAST_UPDATE, Tables.API_TRUST_IDENTITIES + "." + + ApiTrustIdentity.LAST_UPDATED + " AS " + EmailStatus.TRUST_ID_LAST_UPDATE); qb.setProjectionMap(projectionMap); if (projection == null) { @@ -189,13 +197,18 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC + Tables.USER_PACKETS + "." + UserPackets.USER_ID + " IS NOT NULL" + " AND " + Tables.USER_PACKETS + "." + UserPackets.EMAIL + " LIKE " + TEMP_TABLE_QUERIED_ADDRESSES + "." + TEMP_TABLE_COLUMN_ADDRES + ")" + + " LEFT JOIN " + Tables.API_TRUST_IDENTITIES + " ON (" + + Tables.API_TRUST_IDENTITIES + "." + ApiTrustIdentity.IDENTIFIER + " LIKE queried_addresses.address" + + " AND " + Tables.API_TRUST_IDENTITIES + "." + ApiTrustIdentity.PACKAGE_NAME + " = \"" + callingPackageName + "\"" + + ")" + " LEFT JOIN " + Tables.CERTS + " ON (" - + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " = " + Tables.CERTS + "." + Certs.MASTER_KEY_ID - + " AND " + Tables.USER_PACKETS + "." + UserPackets.RANK + " = " + Tables.CERTS + "." + Certs.RANK + + "(" + Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " = " + Tables.CERTS + "." + Certs.MASTER_KEY_ID + + " AND " + Tables.USER_PACKETS + "." + UserPackets.RANK + " = " + Tables.CERTS + "." + Certs.RANK + ")" + ")" ); // in case there are multiple verifying certificates - groupBy = TEMP_TABLE_QUERIED_ADDRESSES + "." + TEMP_TABLE_COLUMN_ADDRES; + groupBy = TEMP_TABLE_QUERIED_ADDRESSES + "." + TEMP_TABLE_COLUMN_ADDRES + + ", " + Tables.CERTS + "." + UserPackets.MASTER_KEY_ID; List plist = Arrays.asList(projection); if (plist.contains(EmailStatus.USER_ID)) { groupBy += ", " + Tables.USER_PACKETS + "." + UserPackets.USER_ID; @@ -211,6 +224,34 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC // uri to watch is all /key_rings/ uri = KeyRings.CONTENT_URI; + break; + } + + case TRUST_IDENTITY: { + boolean callerIsAllowed = mApiPermissionHelper.isAllowedIgnoreErrors(); + if (!callerIsAllowed) { + throw new AccessControlException("An application must register before use of KeychainExternalProvider!"); + } + + if (projection == null) { + throw new IllegalArgumentException("Please provide a projection!"); + } + + HashMap projectionMap = new HashMap<>(); + projectionMap.put(ApiTrustIdentity._ID, "oid AS " + ApiTrustIdentity._ID); + projectionMap.put(ApiTrustIdentity.IDENTIFIER, ApiTrustIdentity.IDENTIFIER); + projectionMap.put(ApiTrustIdentity.MASTER_KEY_ID, ApiTrustIdentity.MASTER_KEY_ID); + projectionMap.put(ApiTrustIdentity.LAST_UPDATED, ApiTrustIdentity.LAST_UPDATED); + qb.setProjectionMap(projectionMap); + + qb.setTables(Tables.API_TRUST_IDENTITIES); + + // allow access to columns of the calling package exclusively! + qb.appendWhere(Tables.API_TRUST_IDENTITIES + "." + ApiTrustIdentity.PACKAGE_NAME + + " = " + mApiPermissionHelper.getCurrentCallingPackage()); + + qb.appendWhere(Tables.API_TRUST_IDENTITIES + "." + ApiTrustIdentity.IDENTIFIER + " = "); + qb.appendWhereEscapeString(uri.getLastPathSegment()); break; } @@ -273,12 +314,64 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC @Override public Uri insert(@NonNull Uri uri, ContentValues values) { - throw new UnsupportedOperationException(); + Log.v(Constants.TAG, "insert(uri=" + uri + ")"); + + int match = mUriMatcher.match(uri); + if (match != TRUST_IDENTITY) { + throw new UnsupportedOperationException(); + } + + boolean callerIsAllowed = mApiPermissionHelper.isAllowedIgnoreErrors(); + if (!callerIsAllowed) { + throw new AccessControlException("An application must register before use of KeychainExternalProvider!"); + } + + Long masterKeyId = values.getAsLong(ApiTrustIdentity.MASTER_KEY_ID); + if (masterKeyId == null) { + throw new IllegalArgumentException("master_key_id must be a non-null value!"); + } + + ContentValues actualValues = new ContentValues(); + actualValues.put(ApiTrustIdentity.PACKAGE_NAME, mApiPermissionHelper.getCurrentCallingPackage()); + actualValues.put(ApiTrustIdentity.IDENTIFIER, uri.getLastPathSegment()); + actualValues.put(ApiTrustIdentity.MASTER_KEY_ID, masterKeyId); + actualValues.put(ApiTrustIdentity.LAST_UPDATED, new Date().getTime() / 1000); + + SQLiteDatabase db = getDb().getWritableDatabase(); + try { + db.insert(Tables.API_TRUST_IDENTITIES, null, actualValues); + return uri; + } finally { + db.close(); + } } @Override - public int delete(@NonNull Uri uri, String additionalSelection, String[] selectionArgs) { - throw new UnsupportedOperationException(); + public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) { + Log.v(Constants.TAG, "delete(uri=" + uri + ")"); + + int match = mUriMatcher.match(uri); + if (match != TRUST_IDENTITY || selection != null || selectionArgs != null) { + throw new UnsupportedOperationException(); + } + + boolean callerIsAllowed = mApiPermissionHelper.isAllowedIgnoreErrors(); + if (!callerIsAllowed) { + throw new AccessControlException("An application must register before use of KeychainExternalProvider!"); + } + + String actualSelection = ApiTrustIdentity.PACKAGE_NAME + " = ? AND " + ApiTrustIdentity.IDENTIFIER + " = ?"; + String[] actualSelectionArgs = new String[] { + mApiPermissionHelper.getCurrentCallingPackage(), + uri.getLastPathSegment() + }; + + SQLiteDatabase db = getDb().getWritableDatabase(); + try { + return db.delete(Tables.API_TRUST_IDENTITIES, actualSelection, actualSelectionArgs); + } finally { + db.close(); + } } @Override