diff options
16 files changed, 797 insertions, 291 deletions
diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index bbd4b9e63..ee21c19ba 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -806,7 +806,12 @@              android:exported="false"              android:label="@string/keyserver_sync_settings_title" /> -        <!-- Internal classes of the remote APIs (not exported!) --> +        <provider +            android:name=".remote.KeychainExternalProvider" +            android:authorities="${applicationId}.provider.exported" +            android:exported="true" /> + +        <!-- Internal classes of the remote APIs (not exported) -->          <activity              android:name=".remote.ui.RemoteCreateAccountActivity"              android:exported="false" diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiDataAccessObject.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiDataAccessObject.java new file mode 100644 index 000000000..7c8295ad5 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiDataAccessObject.java @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014-2016 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.provider; + + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.OperationApplicationException; +import android.database.Cursor; +import android.net.Uri; +import android.os.RemoteException; + +import org.bouncycastle.bcpg.CompressionAlgorithmTags; +import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants; +import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAllowedKeys; +import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps; +import org.sufficientlysecure.keychain.remote.AccountSettings; +import org.sufficientlysecure.keychain.remote.AppSettings; + + +public class ApiDataAccessObject { + +    private final SimpleContentResolverInterface mQueryInterface; + +    public ApiDataAccessObject(Context context) { +        final ContentResolver contentResolver = context.getContentResolver(); +        mQueryInterface = new SimpleContentResolverInterface() { +            @Override +            public Cursor query(Uri contentUri, String[] projection, String selection, String[] selectionArgs, +                    String sortOrder) { +                return contentResolver.query(contentUri, projection, selection, selectionArgs, sortOrder); +            } + +            @Override +            public Uri insert(Uri contentUri, ContentValues values) { +                return contentResolver.insert(contentUri, values); +            } + +            @Override +            public int update(Uri contentUri, ContentValues values, String where, String[] selectionArgs) { +                return contentResolver.update(contentUri, values, where, selectionArgs); +            } + +            @Override +            public int delete(Uri contentUri, String where, String[] selectionArgs) { +                return contentResolver.delete(contentUri, where, selectionArgs); +            } +        }; +    } + +    public ApiDataAccessObject(SimpleContentResolverInterface queryInterface) { +        mQueryInterface = queryInterface; +    } + +    public ArrayList<String> getRegisteredApiApps() { +        Cursor cursor = mQueryInterface.query(ApiApps.CONTENT_URI, null, null, null, null); + +        ArrayList<String> packageNames = new ArrayList<>(); +        try { +            if (cursor != null) { +                int packageNameCol = cursor.getColumnIndex(ApiApps.PACKAGE_NAME); +                if (cursor.moveToFirst()) { +                    do { +                        packageNames.add(cursor.getString(packageNameCol)); +                    } while (cursor.moveToNext()); +                } +            } +        } finally { +            if (cursor != null) { +                cursor.close(); +            } +        } + +        return packageNames; +    } + +    private ContentValues contentValueForApiApps(AppSettings appSettings) { +        ContentValues values = new ContentValues(); +        values.put(ApiApps.PACKAGE_NAME, appSettings.getPackageName()); +        values.put(ApiApps.PACKAGE_CERTIFICATE, appSettings.getPackageCertificate()); +        return values; +    } + +    private ContentValues contentValueForApiAccounts(AccountSettings accSettings) { +        ContentValues values = new ContentValues(); +        values.put(KeychainContract.ApiAccounts.ACCOUNT_NAME, accSettings.getAccountName()); +        values.put(KeychainContract.ApiAccounts.KEY_ID, accSettings.getKeyId()); + +        // DEPRECATED and thus hardcoded +        values.put(KeychainContract.ApiAccounts.COMPRESSION, CompressionAlgorithmTags.ZLIB); +        values.put(KeychainContract.ApiAccounts.ENCRYPTION_ALGORITHM, +                PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_DEFAULT); +        values.put(KeychainContract.ApiAccounts.HASH_ALORITHM, +                PgpSecurityConstants.OpenKeychainHashAlgorithmTags.USE_DEFAULT); +        return values; +    } + +    public void insertApiApp(AppSettings appSettings) { +        mQueryInterface.insert(ApiApps.CONTENT_URI, +                contentValueForApiApps(appSettings)); +    } + +    public void insertApiAccount(Uri uri, AccountSettings accSettings) { +        mQueryInterface.insert(uri, contentValueForApiAccounts(accSettings)); +    } + +    public void updateApiAccount(Uri uri, AccountSettings accSettings) { +        if (mQueryInterface.update(uri, contentValueForApiAccounts(accSettings), null, +                null) <= 0) { +            throw new RuntimeException(); +        } +    } + +    /** +     * Must be an uri pointing to an account +     */ +    public AppSettings getApiAppSettings(Uri uri) { +        AppSettings settings = null; + +        Cursor cursor = mQueryInterface.query(uri, null, null, null, null); +        try { +            if (cursor != null && cursor.moveToFirst()) { +                settings = new AppSettings(); +                settings.setPackageName(cursor.getString( +                        cursor.getColumnIndex(ApiApps.PACKAGE_NAME))); +                settings.setPackageCertificate(cursor.getBlob( +                        cursor.getColumnIndex(ApiApps.PACKAGE_CERTIFICATE))); +            } +        } finally { +            if (cursor != null) { +                cursor.close(); +            } +        } + +        return settings; +    } + +    public AccountSettings getApiAccountSettings(Uri accountUri) { +        AccountSettings settings = null; + +        Cursor cursor = mQueryInterface.query(accountUri, null, null, null, null); +        try { +            if (cursor != null && cursor.moveToFirst()) { +                settings = new AccountSettings(); + +                settings.setAccountName(cursor.getString( +                        cursor.getColumnIndex(KeychainContract.ApiAccounts.ACCOUNT_NAME))); +                settings.setKeyId(cursor.getLong( +                        cursor.getColumnIndex(KeychainContract.ApiAccounts.KEY_ID))); +            } +        } finally { +            if (cursor != null) { +                cursor.close(); +            } +        } + +        return settings; +    } + +    public Set<Long> getAllKeyIdsForApp(Uri uri) { +        Set<Long> keyIds = new HashSet<>(); + +        Cursor cursor = mQueryInterface.query(uri, null, null, null, null); +        try { +            if (cursor != null) { +                int keyIdColumn = cursor.getColumnIndex(KeychainContract.ApiAccounts.KEY_ID); +                while (cursor.moveToNext()) { +                    keyIds.add(cursor.getLong(keyIdColumn)); +                } +            } +        } finally { +            if (cursor != null) { +                cursor.close(); +            } +        } + +        return keyIds; +    } + +    public HashSet<Long> getAllowedKeyIdsForApp(Uri uri) { +        HashSet<Long> keyIds = new HashSet<>(); + +        Cursor cursor = mQueryInterface.query(uri, null, null, null, null); +        try { +            if (cursor != null) { +                int keyIdColumn = cursor.getColumnIndex(ApiAllowedKeys.KEY_ID); +                while (cursor.moveToNext()) { +                    keyIds.add(cursor.getLong(keyIdColumn)); +                } +            } +        } finally { +            if (cursor != null) { +                cursor.close(); +            } +        } + +        return keyIds; +    } + +    public void saveAllowedKeyIdsForApp(Uri uri, Set<Long> allowedKeyIds) +            throws RemoteException, OperationApplicationException { +        // wipe whole table of allowed keys for this account +        mQueryInterface.delete(uri, null, null); + +        // re-insert allowed key ids +        for (Long keyId : allowedKeyIds) { +            ContentValues values = new ContentValues(); +            values.put(ApiAllowedKeys.KEY_ID, keyId); +            mQueryInterface.insert(uri, values); +        } +    } + +    public void addAllowedKeyIdForApp(Uri uri, long allowedKeyId) { +        ContentValues values = new ContentValues(); +        values.put(ApiAllowedKeys.KEY_ID, allowedKeyId); +        mQueryInterface.insert(uri, values); +    } + +    public byte[] getApiAppCertificate(String packageName) { +        Uri queryUri = ApiApps.buildByPackageNameUri(packageName); + +        String[] projection = new String[]{ApiApps.PACKAGE_CERTIFICATE}; + +        Cursor cursor = mQueryInterface.query(queryUri, projection, null, null, null); +        try { +            byte[] signature = null; +            if (cursor != null && cursor.moveToFirst()) { +                int signatureCol = 0; + +                signature = cursor.getBlob(signatureCol); +            } +            return signature; +        } finally { +            if (cursor != null) { +                cursor.close(); +            } +        } +    } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainExternalContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainExternalContract.java new file mode 100644 index 000000000..a4d35f168 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainExternalContract.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2016 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.provider; + + +import android.net.Uri; +import android.provider.BaseColumns; + +import org.sufficientlysecure.keychain.Constants; + + +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 class EmailStatus implements BaseColumns { +        public static final String EMAIL_ADDRESS = "email_address"; +        public static final String EMAIL_STATUS = "email_status"; + +        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"; +    } + +    private KeychainExternalContract() { +    } + +} 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 87b8a3b65..0cb8e4675 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -1,7 +1,7 @@  /*   * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>   * Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org> - * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * Copyright (C) 2014-2016 Vincent Breitmoser <v.breitmoser@mugenguild.com>   *   * This program is free software: you can redistribute it and/or modify   * it under the terms of the GNU General Public License as published by 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 83e2baf9a..72a3e2ff5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -18,6 +18,18 @@  package org.sufficientlysecure.keychain.provider; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.TimeUnit; +  import android.content.ContentProviderOperation;  import android.content.ContentResolver;  import android.content.ContentValues; @@ -29,19 +41,12 @@ import android.os.RemoteException;  import android.support.annotation.NonNull;  import android.support.v4.util.LongSparseArray; -import org.bouncycastle.bcpg.CompressionAlgorithmTags;  import org.sufficientlysecure.keychain.Constants;  import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; -import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; -import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize; -import org.sufficientlysecure.keychain.util.Preferences;  import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;  import org.sufficientlysecure.keychain.operations.ImportOperation;  import org.sufficientlysecure.keychain.operations.results.ConsolidateResult; +import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;  import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;  import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;  import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; @@ -51,41 +56,29 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;  import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;  import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;  import org.sufficientlysecure.keychain.pgp.KeyRing; -import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants;  import org.sufficientlysecure.keychain.pgp.Progressable;  import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;  import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;  import org.sufficientlysecure.keychain.pgp.WrappedSignature; +import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;  import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAllowedKeys; -import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps; +import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;  import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;  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.UpdatedKeys; -import org.sufficientlysecure.keychain.remote.AccountSettings; -import org.sufficientlysecure.keychain.remote.AppSettings; +import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;  import org.sufficientlysecure.keychain.util.IterableIterator;  import org.sufficientlysecure.keychain.util.Log;  import org.sufficientlysecure.keychain.util.ParcelableFileCache; +import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize; +import org.sufficientlysecure.keychain.util.Preferences;  import org.sufficientlysecure.keychain.util.ProgressFixedScaler;  import org.sufficientlysecure.keychain.util.ProgressScaler;  import org.sufficientlysecure.keychain.util.Utf8Util; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; -import java.util.concurrent.TimeUnit; -  /**   * This class contains high level methods for database access. Despite its   * name, it is not only a helper but actually the main interface for all @@ -1481,191 +1474,6 @@ public class ProviderHelper {          return mContentResolver.insert(UpdatedKeys.CONTENT_URI, values);      } -    public ArrayList<String> getRegisteredApiApps() { -        Cursor cursor = mContentResolver.query(ApiApps.CONTENT_URI, null, null, null, null); - -        ArrayList<String> packageNames = new ArrayList<>(); -        try { -            if (cursor != null) { -                int packageNameCol = cursor.getColumnIndex(ApiApps.PACKAGE_NAME); -                if (cursor.moveToFirst()) { -                    do { -                        packageNames.add(cursor.getString(packageNameCol)); -                    } while (cursor.moveToNext()); -                } -            } -        } finally { -            if (cursor != null) { -                cursor.close(); -            } -        } - -        return packageNames; -    } - -    private ContentValues contentValueForApiApps(AppSettings appSettings) { -        ContentValues values = new ContentValues(); -        values.put(ApiApps.PACKAGE_NAME, appSettings.getPackageName()); -        values.put(ApiApps.PACKAGE_CERTIFICATE, appSettings.getPackageCertificate()); -        return values; -    } - -    private ContentValues contentValueForApiAccounts(AccountSettings accSettings) { -        ContentValues values = new ContentValues(); -        values.put(KeychainContract.ApiAccounts.ACCOUNT_NAME, accSettings.getAccountName()); -        values.put(KeychainContract.ApiAccounts.KEY_ID, accSettings.getKeyId()); - -        // DEPRECATED and thus hardcoded -        values.put(KeychainContract.ApiAccounts.COMPRESSION, CompressionAlgorithmTags.ZLIB); -        values.put(KeychainContract.ApiAccounts.ENCRYPTION_ALGORITHM, -                PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_DEFAULT); -        values.put(KeychainContract.ApiAccounts.HASH_ALORITHM, -                PgpSecurityConstants.OpenKeychainHashAlgorithmTags.USE_DEFAULT); -        return values; -    } - -    public void insertApiApp(AppSettings appSettings) { -        mContentResolver.insert(KeychainContract.ApiApps.CONTENT_URI, -                contentValueForApiApps(appSettings)); -    } - -    public void insertApiAccount(Uri uri, AccountSettings accSettings) { -        mContentResolver.insert(uri, contentValueForApiAccounts(accSettings)); -    } - -    public void updateApiAccount(Uri uri, AccountSettings accSettings) { -        if (mContentResolver.update(uri, contentValueForApiAccounts(accSettings), null, -                null) <= 0) { -            throw new RuntimeException(); -        } -    } - -    /** -     * Must be an uri pointing to an account -     */ -    public AppSettings getApiAppSettings(Uri uri) { -        AppSettings settings = null; - -        Cursor cursor = mContentResolver.query(uri, null, null, null, null); -        try { -            if (cursor != null && cursor.moveToFirst()) { -                settings = new AppSettings(); -                settings.setPackageName(cursor.getString( -                        cursor.getColumnIndex(KeychainContract.ApiApps.PACKAGE_NAME))); -                settings.setPackageCertificate(cursor.getBlob( -                        cursor.getColumnIndex(KeychainContract.ApiApps.PACKAGE_CERTIFICATE))); -            } -        } finally { -            if (cursor != null) { -                cursor.close(); -            } -        } - -        return settings; -    } - -    public AccountSettings getApiAccountSettings(Uri accountUri) { -        AccountSettings settings = null; - -        Cursor cursor = mContentResolver.query(accountUri, null, null, null, null); -        try { -            if (cursor != null && cursor.moveToFirst()) { -                settings = new AccountSettings(); - -                settings.setAccountName(cursor.getString( -                        cursor.getColumnIndex(KeychainContract.ApiAccounts.ACCOUNT_NAME))); -                settings.setKeyId(cursor.getLong( -                        cursor.getColumnIndex(KeychainContract.ApiAccounts.KEY_ID))); -            } -        } finally { -            if (cursor != null) { -                cursor.close(); -            } -        } - -        return settings; -    } - -    public Set<Long> getAllKeyIdsForApp(Uri uri) { -        Set<Long> keyIds = new HashSet<>(); - -        Cursor cursor = mContentResolver.query(uri, null, null, null, null); -        try { -            if (cursor != null) { -                int keyIdColumn = cursor.getColumnIndex(KeychainContract.ApiAccounts.KEY_ID); -                while (cursor.moveToNext()) { -                    keyIds.add(cursor.getLong(keyIdColumn)); -                } -            } -        } finally { -            if (cursor != null) { -                cursor.close(); -            } -        } - -        return keyIds; -    } - -    public HashSet<Long> getAllowedKeyIdsForApp(Uri uri) { -        HashSet<Long> keyIds = new HashSet<>(); - -        Cursor cursor = mContentResolver.query(uri, null, null, null, null); -        try { -            if (cursor != null) { -                int keyIdColumn = cursor.getColumnIndex(KeychainContract.ApiAllowedKeys.KEY_ID); -                while (cursor.moveToNext()) { -                    keyIds.add(cursor.getLong(keyIdColumn)); -                } -            } -        } finally { -            if (cursor != null) { -                cursor.close(); -            } -        } - -        return keyIds; -    } - -    public void saveAllowedKeyIdsForApp(Uri uri, Set<Long> allowedKeyIds) -            throws RemoteException, OperationApplicationException { -        // wipe whole table of allowed keys for this account -        mContentResolver.delete(uri, null, null); - -        // re-insert allowed key ids -        for (Long keyId : allowedKeyIds) { -            ContentValues values = new ContentValues(); -            values.put(ApiAllowedKeys.KEY_ID, keyId); -            mContentResolver.insert(uri, values); -        } -    } - -    public void addAllowedKeyIdForApp(Uri uri, long allowedKeyId) { -        ContentValues values = new ContentValues(); -        values.put(ApiAllowedKeys.KEY_ID, allowedKeyId); -        mContentResolver.insert(uri, values); -    } - -    public byte[] getApiAppCertificate(String packageName) { -        Uri queryUri = ApiApps.buildByPackageNameUri(packageName); - -        String[] projection = new String[]{ApiApps.PACKAGE_CERTIFICATE}; - -        Cursor cursor = mContentResolver.query(queryUri, projection, null, null, null); -        try { -            byte[] signature = null; -            if (cursor != null && cursor.moveToFirst()) { -                int signatureCol = 0; - -                signature = cursor.getBlob(signatureCol); -            } -            return signature; -        } finally { -            if (cursor != null) { -                cursor.close(); -            } -        } -    } -      public ContentResolver getContentResolver() {          return mContentResolver;      } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/SimpleContentResolverInterface.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/SimpleContentResolverInterface.java new file mode 100644 index 000000000..0e4d76aa4 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/SimpleContentResolverInterface.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2016 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.provider; + + +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; + +/** This interface contains the principal methods for database access + * from {#android.content.ContentResolver}. It is used to allow substitution + * of a ContentResolver in DAOs. + * + * @see ApiDataAccessObject + */ +public interface SimpleContentResolverInterface { +    Cursor query(Uri contentUri, String[] projection, String selection, String[] selectionArgs, String sortOrder); + +    Uri insert(Uri contentUri, ContentValues values); + +    int update(Uri contentUri, ContentValues values, String where, String[] selectionArgs); + +    int delete(Uri contentUri, String where, String[] selectionArgs); +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPermissionHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPermissionHelper.java index 7edd8b2b0..3af8e70dd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPermissionHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPermissionHelper.java @@ -33,8 +33,8 @@ import org.openintents.openpgp.OpenPgpError;  import org.openintents.openpgp.util.OpenPgpApi;  import org.sufficientlysecure.keychain.Constants;  import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;  import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.provider.ProviderHelper;  import org.sufficientlysecure.keychain.util.Log;  import java.io.ByteArrayOutputStream; @@ -49,13 +49,13 @@ import java.util.Arrays;  public class ApiPermissionHelper {      private final Context mContext; -    private final ProviderHelper mProviderHelper; +    private final ApiDataAccessObject mApiDao;      private PackageManager mPackageManager; -    public ApiPermissionHelper(Context context) { +    public ApiPermissionHelper(Context context, ApiDataAccessObject apiDao) {          mContext = context;          mPackageManager = context.getPackageManager(); -        mProviderHelper = new ProviderHelper(context); +        mApiDao = apiDao;      }      public static class WrongPackageCertificateException extends Exception { @@ -66,14 +66,24 @@ public class ApiPermissionHelper {          }      } +    /** Returns true iff the caller is allowed, or false on any type of problem. +     * This method should only be used in cases where error handling is dealt with separately. +     */ +    protected boolean isAllowedIgnoreErrors() { +        try { +            return isCallerAllowed(); +        } catch (WrongPackageCertificateException e) { +            return false; +        } +    } +      /**       * Checks if caller is allowed to access the API       *       * @return null if caller is allowed, or a Bundle with a PendingIntent       */ -    protected Intent isAllowed(Intent data) { +    protected Intent isAllowedOrReturnIntent(Intent data) {          ApiPendingIntentFactory piFactory = new ApiPendingIntentFactory(mContext); -          try {              if (isCallerAllowed()) {                  return null; @@ -168,7 +178,7 @@ public class ApiPermissionHelper {          Uri uri = KeychainContract.ApiAccounts.buildByPackageAndAccountUri(currentPkg, accountName); -        return mProviderHelper.getApiAccountSettings(uri); // can be null! +        return mApiDao.getApiAccountSettings(uri); // can be null!      }      @Deprecated @@ -224,7 +234,7 @@ public class ApiPermissionHelper {      private boolean isPackageAllowed(String packageName) throws WrongPackageCertificateException {          Log.d(Constants.TAG, "isPackageAllowed packageName: " + packageName); -        ArrayList<String> allowedPkgs = mProviderHelper.getRegisteredApiApps(); +        ArrayList<String> allowedPkgs = mApiDao.getRegisteredApiApps();          Log.d(Constants.TAG, "allowed: " + allowedPkgs);          // check if package is allowed to use our service @@ -239,7 +249,7 @@ public class ApiPermissionHelper {                  throw new WrongPackageCertificateException(e.getMessage());              } -            byte[] storedCert = mProviderHelper.getApiAppCertificate(packageName); +            byte[] storedCert = mApiDao.getApiAppCertificate(packageName);              if (Arrays.equals(currentCert, storedCert)) {                  Log.d(Constants.TAG,                          "Package certificate is correct! (equals certificate from database)"); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java new file mode 100644 index 000000000..7eb5d558f --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2016 Vincent Breitmoser <look@my.amazin.horse> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.remote; + + +import java.security.AccessControlException; +import java.util.Arrays; +import java.util.HashMap; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.UriMatcher; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteQueryBuilder; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.text.TextUtils; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps; +import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +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.EmailStatus; +import org.sufficientlysecure.keychain.provider.SimpleContentResolverInterface; +import org.sufficientlysecure.keychain.util.Log; + + +public class KeychainExternalProvider extends ContentProvider implements SimpleContentResolverInterface { +    private static final int EMAIL_STATUS = 101; +    private static final int API_APPS = 301; +    private static final int API_APPS_BY_PACKAGE_NAME = 302; + + +    private UriMatcher mUriMatcher; +    private ApiPermissionHelper mApiPermissionHelper; + + +    /** +     * Build and return a {@link UriMatcher} that catches all {@link Uri} variations supported by +     * this {@link ContentProvider}. +     */ +    protected UriMatcher buildUriMatcher() { +        final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH); + +        String authority = KeychainExternalContract.CONTENT_AUTHORITY_EXTERNAL; + +        /** +         * list email_status +         * +         * <pre> +         * email_status/ +         * </pre> +         */ +        matcher.addURI(authority, KeychainExternalContract.BASE_EMAIL_STATUS, EMAIL_STATUS); + +        matcher.addURI(KeychainContract.CONTENT_AUTHORITY, KeychainContract.BASE_API_APPS, API_APPS); +        matcher.addURI(KeychainContract.CONTENT_AUTHORITY, KeychainContract.BASE_API_APPS + "/*", API_APPS_BY_PACKAGE_NAME); + +        return matcher; +    } + +    private KeychainDatabase mKeychainDatabase; + +    /** {@inheritDoc} */ +    @Override +    public boolean onCreate() { +        mUriMatcher = buildUriMatcher(); +        mApiPermissionHelper = new ApiPermissionHelper(getContext(), new ApiDataAccessObject(this)); +        return true; +    } + +    public KeychainDatabase getDb() { +        if(mKeychainDatabase == null) +            mKeychainDatabase = new KeychainDatabase(getContext()); +        return mKeychainDatabase; +    } + +    /** +     * {@inheritDoc} +     */ +    @Override +    public String getType(@NonNull Uri uri) { +        final int match = mUriMatcher.match(uri); +        switch (match) { +            case EMAIL_STATUS: +                return EmailStatus.CONTENT_TYPE; + +            case API_APPS: +                return ApiApps.CONTENT_TYPE; + +            case API_APPS_BY_PACKAGE_NAME: +                return ApiApps.CONTENT_ITEM_TYPE; + +            default: +                throw new UnsupportedOperationException("Unknown uri: " + uri); +        } +    } + +    /** +     * {@inheritDoc} +     */ +    @Override +    public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, +                        String sortOrder) { +        Log.v(Constants.TAG, "query(uri=" + uri + ", proj=" + Arrays.toString(projection) + ")"); + +        SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + +        int match = mUriMatcher.match(uri); + +        String groupBy = null; + +        switch (match) { +            case EMAIL_STATUS: { +                boolean callerIsAllowed = mApiPermissionHelper.isAllowedIgnoreErrors(); +                if (!callerIsAllowed) { +                    throw new AccessControlException("An application must register before use of KeychainExternalProvider!"); +                } + +                HashMap<String, String> projectionMap = new HashMap<>(); +                projectionMap.put(EmailStatus._ID, "email AS _id"); +                projectionMap.put(EmailStatus.EMAIL_ADDRESS, +                        Tables.USER_PACKETS + "." + UserPackets.USER_ID + " AS " + EmailStatus.EMAIL_ADDRESS); +                // we take the minimum (>0) here, where "1" is "verified by known secret key", "2" is "self-certified" +                projectionMap.put(EmailStatus.EMAIL_STATUS, "CASE ( MIN (" + Certs.VERIFIED + " ) ) " +                        // remap to keep this provider contract independent from our internal representation +                        + " WHEN " + Certs.VERIFIED_SELF + " THEN 1" +                        + " WHEN " + Certs.VERIFIED_SECRET + " THEN 2" +                        + " END AS " + EmailStatus.EMAIL_STATUS); +                qb.setProjectionMap(projectionMap); + +                if (projection == null) { +                    throw new IllegalArgumentException("Please provide a projection!"); +                } + +                qb.setTables( +                        Tables.USER_PACKETS +                                + " INNER 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 +                                // verified == 0 has no self-cert, which is basically an error case. never return that! +                                + " AND " + Tables.CERTS + "." + Certs.VERIFIED + " > 0" +                                + ")" +                ); +                qb.appendWhere(Tables.USER_PACKETS + "." + UserPackets.USER_ID + " IS NOT NULL"); +                // in case there are multiple verifying certificates +                groupBy = Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + ", " +                        + Tables.USER_PACKETS + "." + UserPackets.USER_ID; + +                if (TextUtils.isEmpty(sortOrder)) { +                    sortOrder =  EmailStatus.EMAIL_ADDRESS + " ASC, " + EmailStatus.EMAIL_STATUS + " DESC"; +                } + +                // uri to watch is all /key_rings/ +                uri = KeyRings.CONTENT_URI; + +                boolean gotCondition = false; +                String emailWhere = ""; +                // JAVA ♥ +                for (int i = 0; i < selectionArgs.length; ++i) { +                    if (selectionArgs[i].length() == 0) { +                        continue; +                    } +                    if (i != 0) { +                        emailWhere += " OR "; +                    } +                    emailWhere += UserPackets.USER_ID + " LIKE "; +                    // match '*<email>', so it has to be at the *end* of the user id +                    emailWhere += DatabaseUtils.sqlEscapeString("%<" + selectionArgs[i] + ">"); +                    gotCondition = true; +                } + +                if (gotCondition) { +                    qb.appendWhere(" AND (" + emailWhere + ")"); +                } else { +                    // TODO better way to do this? +                    Log.e(Constants.TAG, "Malformed find by email query!"); +                    qb.appendWhere(" AND 0"); +                } + +                break; +            } + +            case API_APPS: { +                qb.setTables(Tables.API_APPS); + +                break; +            } + +            case API_APPS_BY_PACKAGE_NAME: { +                qb.setTables(Tables.API_APPS); +                qb.appendWhere(ApiApps.PACKAGE_NAME + " = "); +                qb.appendWhereEscapeString(uri.getLastPathSegment()); + +                break; +            } + +            default: { +                throw new IllegalArgumentException("Unknown URI " + uri + " (" + match + ")"); +            } + +        } + +        // If no sort order is specified use the default +        String orderBy; +        if (TextUtils.isEmpty(sortOrder)) { +            orderBy = null; +        } else { +            orderBy = sortOrder; +        } + +        SQLiteDatabase db = getDb().getReadableDatabase(); + +        Cursor cursor = qb.query(db, projection, selection, null, groupBy, null, orderBy); +        if (cursor != null) { +            // Tell the cursor what uri to watch, so it knows when its source data changes +            cursor.setNotificationUri(getContext().getContentResolver(), uri); +        } + +        Log.d(Constants.TAG, +                "Query: " + qb.buildQuery(projection, selection, null, null, orderBy, null)); + +        return cursor; +    } + +    @Override +    public Uri insert(@NonNull Uri uri, ContentValues values) { +        throw new UnsupportedOperationException(); +    } + +    @Override +    public int delete(@NonNull Uri uri, String additionalSelection, String[] selectionArgs) { +        throw new UnsupportedOperationException(); +    } + +    @Override +    public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) { +        throw new UnsupportedOperationException(); +    } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index 2e14099de..10b137b4f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -1,5 +1,6 @@  /*   * Copyright (C) 2013-2015 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2016 Vincent Breitmoser <look@my.amazin.horse>   *   * This program is free software: you can redistribute it and/or modify   * it under the terms of the GNU General Public License as published by @@ -17,6 +18,17 @@  package org.sufficientlysecure.keychain.remote; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +  import android.app.PendingIntent;  import android.app.Service;  import android.content.Intent; @@ -41,12 +53,15 @@ import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;  import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEntryParcel;  import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;  import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; +import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.pgp.KeyRing.UserId;  import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;  import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation;  import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants;  import org.sufficientlysecure.keychain.pgp.PgpSignEncryptInputParcel;  import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation;  import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; +import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;  import org.sufficientlysecure.keychain.provider.KeychainContract;  import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAccounts;  import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; @@ -58,18 +73,9 @@ import org.sufficientlysecure.keychain.util.InputData;  import org.sufficientlysecure.keychain.util.Log;  import org.sufficientlysecure.keychain.util.Passphrase; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.HashSet; -import java.util.List; -  public class OpenPgpService extends Service { -    static final String[] EMAIL_SEARCH_PROJECTION = new String[]{ +    static final String[] KEY_SEARCH_PROJECTION = new String[]{              KeyRings._ID,              KeyRings.MASTER_KEY_ID,              KeyRings.IS_EXPIRED, @@ -77,35 +83,50 @@ public class OpenPgpService extends Service {      };      // do not pre-select revoked or expired keys -    static final String EMAIL_SEARCH_WHERE = Tables.KEYS + "." + KeychainContract.KeyRings.IS_REVOKED +    static final String KEY_SEARCH_WHERE = Tables.KEYS + "." + KeychainContract.KeyRings.IS_REVOKED              + " = 0 AND " + KeychainContract.KeyRings.IS_EXPIRED + " = 0";      private ApiPermissionHelper mApiPermissionHelper;      private ProviderHelper mProviderHelper; +    private ApiDataAccessObject mApiDao;      @Override      public void onCreate() {          super.onCreate(); -        mApiPermissionHelper = new ApiPermissionHelper(this); +        mApiPermissionHelper = new ApiPermissionHelper(this, new ApiDataAccessObject(this));          mProviderHelper = new ProviderHelper(this); +        mApiDao = new ApiDataAccessObject(this);      } -    /** -     * Search database for key ids based on emails. -     */ -    private Intent returnKeyIdsFromEmails(Intent data, String[] encryptionUserIds) { +    private static class KeyIdResult { +        final Intent mRequiredUserInteraction; +        final HashSet<Long> mKeyIds; + +        KeyIdResult(Intent requiredUserInteraction) { +            mRequiredUserInteraction = requiredUserInteraction; +            mKeyIds = null; +        } +        KeyIdResult(HashSet<Long> keyIds) { +            mRequiredUserInteraction = null; +            mKeyIds = keyIds; +        } +    } + +    private KeyIdResult returnKeyIdsFromEmails(Intent data, String[] encryptionUserIds) {          boolean noUserIdsCheck = (encryptionUserIds == null || encryptionUserIds.length == 0);          boolean missingUserIdsCheck = false;          boolean duplicateUserIdsCheck = false; -        ArrayList<Long> keyIds = new ArrayList<>(); +        HashSet<Long> keyIds = new HashSet<>();          ArrayList<String> missingEmails = new ArrayList<>();          ArrayList<String> duplicateEmails = new ArrayList<>();          if (!noUserIdsCheck) { -            for (String email : encryptionUserIds) { +            for (String rawUserId : encryptionUserIds) { +                UserId userId = KeyRing.splitUserId(rawUserId); +                String email = userId.email != null ? userId.email : rawUserId;                  // try to find the key for this specific email                  Uri uri = KeyRings.buildUnifiedKeyRingsFindByEmailUri(email); -                Cursor cursor = getContentResolver().query(uri, EMAIL_SEARCH_PROJECTION, EMAIL_SEARCH_WHERE, null, null); +                Cursor cursor = getContentResolver().query(uri, KEY_SEARCH_PROJECTION, KEY_SEARCH_WHERE, null, null);                  try {                      // result should be one entry containing the key id                      if (cursor != null && cursor.moveToFirst()) { @@ -134,15 +155,11 @@ public class OpenPgpService extends Service {              }          } -        // convert ArrayList<Long> to long[] -        long[] keyIdsArray = new long[keyIds.size()]; -        for (int i = 0; i < keyIdsArray.length; i++) { -            keyIdsArray[i] = keyIds.get(i); -        } -          if (noUserIdsCheck || missingUserIdsCheck || duplicateUserIdsCheck) {              // allow the user to verify pub key selection +            // convert ArrayList<Long> to long[] +            long[] keyIdsArray = getUnboxedLongArray(keyIds);              ApiPendingIntentFactory piFactory = new ApiPendingIntentFactory(getBaseContext());              PendingIntent pi = piFactory.createSelectPublicKeyPendingIntent(data, keyIdsArray,                      missingEmails, duplicateEmails, noUserIdsCheck); @@ -151,18 +168,15 @@ public class OpenPgpService extends Service {              Intent result = new Intent();              result.putExtra(OpenPgpApi.RESULT_INTENT, pi);              result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED); -            return result; +            return new KeyIdResult(result);          } else {              // everything was easy, we have exactly one key for every email -            if (keyIdsArray.length == 0) { +            if (keyIds.isEmpty()) {                  Log.e(Constants.TAG, "keyIdsArray.length == 0, should never happen!");              } -            Intent result = new Intent(); -            result.putExtra(OpenPgpApi.RESULT_KEY_IDS, keyIdsArray); -            result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); -            return result; +            return new KeyIdResult(keyIds);          }      } @@ -277,20 +291,31 @@ public class OpenPgpService extends Service {                  compressionId = PgpSecurityConstants.OpenKeychainCompressionAlgorithmTags.UNCOMPRESSED;              } -            // first try to get key ids from non-ambiguous key id extra -            long[] keyIds = data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS); -            if (keyIds == null) { +            long[] keyIds; +            { +                HashSet<Long> encryptKeyIds = new HashSet<>(); +                  // get key ids based on given user ids -                String[] userIds = data.getStringArrayExtra(OpenPgpApi.EXTRA_USER_IDS); -                // give params through to activity... -                Intent result = returnKeyIdsFromEmails(data, userIds); +                if (data.hasExtra(OpenPgpApi.EXTRA_USER_IDS)) { +                    String[] userIds = data.getStringArrayExtra(OpenPgpApi.EXTRA_USER_IDS); +                    data.removeExtra(OpenPgpApi.EXTRA_USER_IDS); +                    // give params through to activity... +                    KeyIdResult result = returnKeyIdsFromEmails(data, userIds); + +                    if (result.mRequiredUserInteraction != null) { +                        return result.mRequiredUserInteraction; +                    } +                    encryptKeyIds.addAll(result.mKeyIds); +                } -                if (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0) == OpenPgpApi.RESULT_CODE_SUCCESS) { -                    keyIds = result.getLongArrayExtra(OpenPgpApi.RESULT_KEY_IDS); -                } else { -                    // if not success -> result contains a PendingIntent for user interaction -                    return result; +                // add key ids from non-ambiguous key id extra +                if (data.hasExtra(OpenPgpApi.EXTRA_KEY_IDS)) { +                    for (long keyId : data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS)) { +                        encryptKeyIds.add(keyId); +                    }                  } + +                keyIds = getUnboxedLongArray(encryptKeyIds);              }              // TODO this is not correct! @@ -402,11 +427,11 @@ public class OpenPgpService extends Service {              }              String currentPkg = mApiPermissionHelper.getCurrentCallingPackage(); -            HashSet<Long> allowedKeyIds = mProviderHelper.getAllowedKeyIdsForApp( +            HashSet<Long> allowedKeyIds = mApiDao.getAllowedKeyIdsForApp(                      KeychainContract.ApiAllowedKeys.buildBaseUri(currentPkg));              if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) < 7) { -                allowedKeyIds.addAll(mProviderHelper.getAllKeyIdsForApp( +                allowedKeyIds.addAll(mApiDao.getAllKeyIdsForApp(                          ApiAccounts.buildBaseUri(currentPkg)));              } @@ -666,10 +691,45 @@ public class OpenPgpService extends Service {          } else {              // get key ids based on given user ids              String[] userIds = data.getStringArrayExtra(OpenPgpApi.EXTRA_USER_IDS); -            return returnKeyIdsFromEmails(data, userIds); +            data.removeExtra(OpenPgpApi.EXTRA_USER_IDS); +            KeyIdResult keyResult = returnKeyIdsFromEmails(data, userIds); +            if (keyResult.mRequiredUserInteraction != null) { +                return keyResult.mRequiredUserInteraction; +            } + +            if (keyResult.mKeyIds == null) { +                throw new AssertionError("one of requiredUserInteraction and keyIds must be non-null, this is a bug!"); +            } + +            long[] keyIds = getUnboxedLongArray(keyResult.mKeyIds); + +            Intent resultIntent = new Intent(); +            resultIntent.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); +            resultIntent.putExtra(OpenPgpApi.RESULT_KEY_IDS, keyIds); +            return resultIntent;          }      } +    @NonNull +    private static long[] getUnboxedLongArray(@NonNull Collection<Long> arrayList) { +        long[] result = new long[arrayList.size()]; +        int i = 0; +        for (Long e : arrayList) { +            result[i++] = e; +        } +        return result; +    } + +    private Intent checkPermissionImpl(@NonNull Intent data) { +        Intent permissionIntent = mApiPermissionHelper.isAllowedOrReturnIntent(data); +        if (permissionIntent != null) { +            return permissionIntent; +        } +        Intent result = new Intent(); +        result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS); +        return result; +    } +      private Intent getSignKeyMasterId(Intent data) {          // NOTE: Accounts are deprecated on API version >= 7          if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) < 7) { @@ -732,7 +792,7 @@ public class OpenPgpService extends Service {          }          // check if caller is allowed to access OpenKeychain -        Intent result = mApiPermissionHelper.isAllowed(data); +        Intent result = mApiPermissionHelper.isAllowedOrReturnIntent(data);          if (result != null) {              return result;          } @@ -799,6 +859,9 @@ public class OpenPgpService extends Service {          String action = data.getAction();          switch (action) { +            case OpenPgpApi.ACTION_CHECK_PERMISSION: { +                return checkPermissionImpl(data); +            }              case OpenPgpApi.ACTION_CLEARTEXT_SIGN: {                  return signImpl(data, inputStream, outputStream, true);              } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java index e19757d65..27700b5e6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java @@ -29,7 +29,7 @@ import org.sufficientlysecure.keychain.R;  import org.sufficientlysecure.keychain.operations.results.OperationResult;  import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;  import org.sufficientlysecure.keychain.operations.results.SingletonResult; -import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;  import org.sufficientlysecure.keychain.remote.AccountSettings;  import org.sufficientlysecure.keychain.ui.base.BaseActivity;  import org.sufficientlysecure.keychain.util.Log; @@ -98,7 +98,7 @@ public class AccountSettingsActivity extends BaseActivity {      }      private void loadData(Uri accountUri) { -        AccountSettings settings = new ProviderHelper(this).getApiAccountSettings(accountUri); +        AccountSettings settings = new ApiDataAccessObject(this).getApiAccountSettings(accountUri);          mAccountSettingsFragment.setAccSettings(settings);      } @@ -110,7 +110,7 @@ public class AccountSettingsActivity extends BaseActivity {      }      private void save() { -        new ProviderHelper(this).updateApiAccount(mAccountUri, mAccountSettingsFragment.getAccSettings()); +        new ApiDataAccessObject(this).updateApiAccount(mAccountUri, mAccountSettingsFragment.getAccSettings());          SingletonResult result = new SingletonResult(                  SingletonResult.RESULT_OK, LogType.MSG_ACC_SAVED);          Intent intent = new Intent(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java index 3b5fdfd8a..249c0c208 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java @@ -37,8 +37,8 @@ import org.bouncycastle.util.encoders.Hex;  import org.sufficientlysecure.keychain.Constants;  import org.sufficientlysecure.keychain.R;  import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;  import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.provider.ProviderHelper;  import org.sufficientlysecure.keychain.remote.AppSettings;  import org.sufficientlysecure.keychain.ui.base.BaseActivity;  import org.sufficientlysecure.keychain.ui.dialog.AdvancedAppSettingsDialogFragment; @@ -182,7 +182,7 @@ public class AppSettingsActivity extends BaseActivity {      }      private void loadData(Bundle savedInstanceState, Uri appUri) { -        mAppSettings = new ProviderHelper(this).getApiAppSettings(appUri); +        mAppSettings = new ApiDataAccessObject(this).getApiAppSettings(appUri);          // get application name and icon from package manager          String appName; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java index caa173f03..22a03a7c5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java @@ -36,8 +36,8 @@ import android.widget.ListView;  import org.sufficientlysecure.keychain.Constants;  import org.sufficientlysecure.keychain.R;  import org.sufficientlysecure.keychain.compatibility.ListFragmentWorkaround; +import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;  import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.provider.ProviderHelper;  import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;  import org.sufficientlysecure.keychain.ui.adapter.KeySelectableAdapter;  import org.sufficientlysecure.keychain.ui.widget.FixedListView; @@ -47,7 +47,7 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i      private static final String ARG_DATA_URI = "uri";      private KeySelectableAdapter mAdapter; -    private ProviderHelper mProviderHelper; +    private ApiDataAccessObject mApiDao;      private Uri mDataUri; @@ -69,7 +69,7 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i      public void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState); -        mProviderHelper = new ProviderHelper(getActivity()); +        mApiDao = new ApiDataAccessObject(getActivity());      }      @Override @@ -107,7 +107,7 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i          // application this would come from a resource.          setEmptyText(getString(R.string.list_empty)); -        Set<Long> checked = mProviderHelper.getAllKeyIdsForApp(mDataUri); +        Set<Long> checked = mApiDao.getAllKeyIdsForApp(mDataUri);          mAdapter = new KeySelectableAdapter(getActivity(), null, 0, checked);          setListAdapter(mAdapter);          getListView().setOnItemClickListener(mAdapter); @@ -141,7 +141,7 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i      public void saveAllowedKeys() {          try { -            mProviderHelper.saveAllowedKeyIdsForApp(mDataUri, getSelectedMasterKeyIds()); +            mApiDao.saveAllowedKeyIdsForApp(mDataUri, getSelectedMasterKeyIds());          } catch (RemoteException | OperationApplicationException e) {              Log.e(Constants.TAG, "Problem saving allowed key ids!", e);          } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteCreateAccountActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteCreateAccountActivity.java index adc1f5abf..a4a0b242c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteCreateAccountActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteCreateAccountActivity.java @@ -17,6 +17,7 @@  package org.sufficientlysecure.keychain.remote.ui; +  import android.content.Intent;  import android.net.Uri;  import android.os.Bundle; @@ -25,8 +26,8 @@ import android.widget.TextView;  import org.sufficientlysecure.keychain.Constants;  import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;  import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.provider.ProviderHelper;  import org.sufficientlysecure.keychain.remote.AccountSettings;  import org.sufficientlysecure.keychain.ui.base.BaseActivity;  import org.sufficientlysecure.keychain.ui.util.Notify; @@ -56,7 +57,7 @@ public class RemoteCreateAccountActivity extends BaseActivity {          final String packageName = extras.getString(EXTRA_PACKAGE_NAME);          final String accName = extras.getString(EXTRA_ACC_NAME); -        final ProviderHelper providerHelper = new ProviderHelper(this); +        final ApiDataAccessObject apiDao = new ApiDataAccessObject(this);          mAccSettingsFragment = (AccountSettingsFragment) getSupportFragmentManager().findFragmentById(                  R.id.api_account_settings_fragment); @@ -65,7 +66,7 @@ public class RemoteCreateAccountActivity extends BaseActivity {          // update existing?          Uri uri = KeychainContract.ApiAccounts.buildByPackageAndAccountUri(packageName, accName); -        AccountSettings settings = providerHelper.getApiAccountSettings(uri); +        AccountSettings settings = apiDao.getApiAccountSettings(uri);          if (settings == null) {              // create new account              settings = new AccountSettings(accName); @@ -94,11 +95,11 @@ public class RemoteCreateAccountActivity extends BaseActivity {                              if (mUpdateExistingAccount) {                                  Uri baseUri = KeychainContract.ApiAccounts.buildBaseUri(packageName);                                  Uri accountUri = baseUri.buildUpon().appendEncodedPath(accName).build(); -                                providerHelper.updateApiAccount( +                                apiDao.updateApiAccount(                                          accountUri,                                          mAccSettingsFragment.getAccSettings());                              } else { -                                providerHelper.insertApiAccount( +                                apiDao.insertApiAccount(                                          KeychainContract.ApiAccounts.buildBaseUri(packageName),                                          mAccSettingsFragment.getAccSettings());                              } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteRegisterActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteRegisterActivity.java index d31f9e35a..0ce9e66c9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteRegisterActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteRegisterActivity.java @@ -17,13 +17,14 @@  package org.sufficientlysecure.keychain.remote.ui; +  import android.content.Intent;  import android.os.Bundle;  import android.view.View;  import org.sufficientlysecure.keychain.Constants;  import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;  import org.sufficientlysecure.keychain.remote.AppSettings;  import org.sufficientlysecure.keychain.ui.base.BaseActivity;  import org.sufficientlysecure.keychain.util.Log; @@ -52,7 +53,7 @@ public class RemoteRegisterActivity extends BaseActivity {          final byte[] packageSignature = extras.getByteArray(EXTRA_PACKAGE_SIGNATURE);          Log.d(Constants.TAG, "ACTION_REGISTER packageName: " + packageName); -        final ProviderHelper providerHelper = new ProviderHelper(this); +        final ApiDataAccessObject apiDao = new ApiDataAccessObject(this);          mAppSettingsHeaderFragment = (AppSettingsHeaderFragment) getSupportFragmentManager().findFragmentById(                  R.id.api_app_settings_fragment); @@ -67,8 +68,7 @@ public class RemoteRegisterActivity extends BaseActivity {                      @Override                      public void onClick(View v) {                          // Allow - -                        providerHelper.insertApiApp(mAppSettingsHeaderFragment.getAppSettings()); +                        apiDao.insertApiApp(mAppSettingsHeaderFragment.getAppSettings());                          // give data through for new service call                          Intent resultData = extras.getParcelable(EXTRA_DATA); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java index 852e8bb81..73ce5fe47 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java @@ -17,6 +17,7 @@  package org.sufficientlysecure.keychain.remote.ui; +  import android.app.Activity;  import android.content.Context;  import android.content.Intent; @@ -36,9 +37,9 @@ import org.openintents.openpgp.util.OpenPgpApi;  import org.sufficientlysecure.keychain.Constants;  import org.sufficientlysecure.keychain.R;  import org.sufficientlysecure.keychain.compatibility.ListFragmentWorkaround; +import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;  import org.sufficientlysecure.keychain.provider.KeychainContract;  import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.provider.ProviderHelper;  import org.sufficientlysecure.keychain.ui.adapter.SelectKeyCursorAdapter;  import org.sufficientlysecure.keychain.ui.widget.FixedListView;  import org.sufficientlysecure.keychain.util.Log; @@ -49,7 +50,7 @@ public class SelectSignKeyIdListFragment extends ListFragmentWorkaround implemen      public static final String ARG_DATA = "data";      private SelectKeyCursorAdapter mAdapter; -    private ProviderHelper mProviderHelper; +    private ApiDataAccessObject mApiDao;      private Uri mDataUri; @@ -72,7 +73,7 @@ public class SelectSignKeyIdListFragment extends ListFragmentWorkaround implemen      public void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState); -        mProviderHelper = new ProviderHelper(getActivity()); +        mApiDao = new ApiDataAccessObject(getActivity());      }      @Override @@ -116,7 +117,7 @@ public class SelectSignKeyIdListFragment extends ListFragmentWorkaround implemen                  Uri allowedKeysUri = mDataUri.buildUpon().appendPath(KeychainContract.PATH_ALLOWED_KEYS).build();                  Log.d(Constants.TAG, "allowedKeysUri: " + allowedKeysUri); -                mProviderHelper.addAllowedKeyIdForApp(allowedKeysUri, masterKeyId); +                mApiDao.addAllowedKeyIdForApp(allowedKeysUri, masterKeyId);                  resultData.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, masterKeyId); diff --git a/extern/openpgp-api-lib b/extern/openpgp-api-lib -Subproject 075616c461f5ce2bd76a4078c31a51a6ee6b860 +Subproject e177b56ab36056f6e79fc0ae4dc4875c9a2941f  | 
