aboutsummaryrefslogtreecommitdiffstats
path: root/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPermissionHelper.java
diff options
context:
space:
mode:
authorVincent Breitmoser <valodim@mugenguild.com>2015-11-27 23:40:04 +0100
committerVincent Breitmoser <valodim@mugenguild.com>2015-11-27 23:40:04 +0100
commitdf219b61c6416b56e52b32d58d828e5b347512cb (patch)
tree259d1ea71fee5f6a1481b20ba49f694e9ee5afed /OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPermissionHelper.java
parent984c91d62c3a91223601b9da8c71582cd1311363 (diff)
downloadopen-keychain-df219b61c6416b56e52b32d58d828e5b347512cb.tar.gz
open-keychain-df219b61c6416b56e52b32d58d828e5b347512cb.tar.bz2
open-keychain-df219b61c6416b56e52b32d58d828e5b347512cb.zip
move custm api permission methods into helper class (composition over inheritance)
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPermissionHelper.java')
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPermissionHelper.java280
1 files changed, 280 insertions, 0 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPermissionHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPermissionHelper.java
new file mode 100644
index 000000000..2d65e5b80
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPermissionHelper.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2013-2015 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * 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.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import android.annotation.SuppressLint;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.Signature;
+import android.net.Uri;
+import android.os.Binder;
+
+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.KeychainContract;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.remote.ui.RemoteServiceActivity;
+import org.sufficientlysecure.keychain.util.Log;
+
+
+/**
+ * Abstract service class for remote APIs that handle app registration and user input.
+ */
+public class ApiPermissionHelper {
+
+ private final Context mContext;
+ private final ProviderHelper mProviderHelper;
+ private PackageManager mPackageManager;
+
+ public ApiPermissionHelper(Context context) {
+ mContext = context;
+ mPackageManager = context.getPackageManager();
+ mProviderHelper = new ProviderHelper(context);
+ }
+
+ public static class WrongPackageCertificateException extends Exception {
+ private static final long serialVersionUID = -8294642703122196028L;
+
+ public WrongPackageCertificateException(String message) {
+ super(message);
+ }
+ }
+
+ /**
+ * 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) {
+ try {
+ if (isCallerAllowed()) {
+ return null;
+ } else {
+ String packageName = getCurrentCallingPackage();
+ Log.d(Constants.TAG, "isAllowed packageName: " + packageName);
+
+ byte[] packageCertificate;
+ try {
+ packageCertificate = getPackageCertificate(packageName);
+ } catch (NameNotFoundException e) {
+ Log.e(Constants.TAG, "Should not happen, returning!", e);
+ // return error
+ Intent result = new Intent();
+ result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
+ result.putExtra(OpenPgpApi.RESULT_ERROR,
+ new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
+ return result;
+ }
+ Log.e(Constants.TAG, "Not allowed to use service! return PendingIntent for registration!");
+
+ Intent intent = new Intent(mContext, RemoteServiceActivity.class);
+ intent.setAction(RemoteServiceActivity.ACTION_REGISTER);
+ intent.putExtra(RemoteServiceActivity.EXTRA_PACKAGE_NAME, packageName);
+ intent.putExtra(RemoteServiceActivity.EXTRA_PACKAGE_SIGNATURE, packageCertificate);
+ intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
+
+ PendingIntent pi = PendingIntent.getActivity(mContext, 0,
+ intent,
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
+
+ // return PendingIntent to be executed by client
+ Intent result = new Intent();
+ result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
+ result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
+
+ return result;
+ }
+ } catch (WrongPackageCertificateException e) {
+ Log.e(Constants.TAG, "wrong signature!", e);
+
+ Intent intent = new Intent(mContext, RemoteServiceActivity.class);
+ intent.setAction(RemoteServiceActivity.ACTION_ERROR_MESSAGE);
+ intent.putExtra(RemoteServiceActivity.EXTRA_ERROR_MESSAGE,
+ mContext.getString(R.string.api_error_wrong_signature));
+ intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
+
+ PendingIntent pi = PendingIntent.getActivity(mContext, 0,
+ intent,
+ PendingIntent.FLAG_CANCEL_CURRENT);
+
+ // return PendingIntent to be executed by client
+ Intent result = new Intent();
+ result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
+ result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
+
+ return result;
+ }
+ }
+
+ private byte[] getPackageCertificate(String packageName) throws NameNotFoundException {
+ @SuppressLint("PackageManagerGetSignatures") // we do check the byte array of *all* signatures
+ PackageInfo pkgInfo = mContext.getPackageManager().getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
+ // NOTE: Silly Android API naming: Signatures are actually certificates
+ Signature[] certificates = pkgInfo.signatures;
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ for (Signature cert : certificates) {
+ try {
+ outputStream.write(cert.toByteArray());
+ } catch (IOException e) {
+ throw new RuntimeException("Should not happen! Writing ByteArrayOutputStream to concat certificates failed");
+ }
+ }
+
+ // Even if an apk has several certificates, these certificates should never change
+ // Google Play does not allow the introduction of new certificates into an existing apk
+ // Also see this attack: http://stackoverflow.com/a/10567852
+ return outputStream.toByteArray();
+ }
+
+ /**
+ * Returns package name associated with the UID, which is assigned to the process that sent you the
+ * current transaction that is being processed :)
+ *
+ * @return package name
+ */
+ protected String getCurrentCallingPackage() {
+ String[] callingPackages = mPackageManager.getPackagesForUid(Binder.getCallingUid());
+
+ // NOTE: No support for sharedUserIds
+ // callingPackages contains more than one entry when sharedUserId has been used
+ // No plans to support sharedUserIds due to many bugs connected to them:
+ // http://java-hamster.blogspot.de/2010/05/androids-shareduserid.html
+ String currentPkg = callingPackages[0];
+ Log.d(Constants.TAG, "currentPkg: " + currentPkg);
+
+ return currentPkg;
+ }
+
+ /**
+ * DEPRECATED API
+ * <p/>
+ * Retrieves AccountSettings from database for the application calling this remote service
+ */
+ protected AccountSettings getAccSettings(String accountName) {
+ String currentPkg = getCurrentCallingPackage();
+ Log.d(Constants.TAG, "getAccSettings accountName: " + accountName);
+
+ Uri uri = KeychainContract.ApiAccounts.buildByPackageAndAccountUri(currentPkg, accountName);
+
+ return mProviderHelper.getApiAccountSettings(uri); // can be null!
+ }
+
+ /**
+ * Deprecated API
+ */
+ protected Intent getCreateAccountIntent(Intent data, String accountName) {
+ String packageName = getCurrentCallingPackage();
+ Log.d(Constants.TAG, "getCreateAccountIntent accountName: " + accountName);
+
+ Intent intent = new Intent(mContext, RemoteServiceActivity.class);
+ intent.setAction(RemoteServiceActivity.ACTION_CREATE_ACCOUNT);
+ intent.putExtra(RemoteServiceActivity.EXTRA_PACKAGE_NAME, packageName);
+ intent.putExtra(RemoteServiceActivity.EXTRA_ACC_NAME, accountName);
+ intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
+
+ PendingIntent pi = PendingIntent.getActivity(mContext, 0,
+ intent,
+ PendingIntent.FLAG_CANCEL_CURRENT);
+
+ // return PendingIntent to be executed by client
+ Intent result = new Intent();
+ result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
+ result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
+
+ return result;
+ }
+
+ /**
+ * Checks if process that binds to this service (i.e. the package name corresponding to the
+ * process) is in the list of allowed package names.
+ *
+ * @return true if process is allowed to use this service
+ * @throws WrongPackageCertificateException
+ */
+ private boolean isCallerAllowed() throws WrongPackageCertificateException {
+ return isUidAllowed(Binder.getCallingUid());
+ }
+
+ private boolean isUidAllowed(int uid)
+ throws WrongPackageCertificateException {
+
+ String[] callingPackages = mPackageManager.getPackagesForUid(uid);
+
+ // is calling package allowed to use this service?
+ for (String currentPkg : callingPackages) {
+ if (isPackageAllowed(currentPkg)) {
+ return true;
+ }
+ }
+
+ Log.e(Constants.TAG, "Uid is NOT allowed!");
+ return false;
+ }
+
+ /**
+ * Checks if packageName is a registered app for the API. Does not return true for own package!
+ *
+ * @throws WrongPackageCertificateException
+ */
+ private boolean isPackageAllowed(String packageName) throws WrongPackageCertificateException {
+ Log.d(Constants.TAG, "isPackageAllowed packageName: " + packageName);
+
+ ArrayList<String> allowedPkgs = mProviderHelper.getRegisteredApiApps();
+ Log.d(Constants.TAG, "allowed: " + allowedPkgs);
+
+ // check if package is allowed to use our service
+ if (allowedPkgs.contains(packageName)) {
+ Log.d(Constants.TAG, "Package is allowed! packageName: " + packageName);
+
+ // check package signature
+ byte[] currentCert;
+ try {
+ currentCert = getPackageCertificate(packageName);
+ } catch (NameNotFoundException e) {
+ throw new WrongPackageCertificateException(e.getMessage());
+ }
+
+ byte[] storedCert = mProviderHelper.getApiAppCertificate(packageName);
+ if (Arrays.equals(currentCert, storedCert)) {
+ Log.d(Constants.TAG,
+ "Package certificate is correct! (equals certificate from database)");
+ return true;
+ } else {
+ throw new WrongPackageCertificateException(
+ "PACKAGE NOT ALLOWED! Certificate wrong! (Certificate not " +
+ "equals certificate from database)");
+ }
+ }
+
+ Log.d(Constants.TAG, "Package is NOT allowed! packageName: " + packageName);
+ return false;
+ }
+
+}