aboutsummaryrefslogtreecommitdiffstats
path: root/OpenPGP-Keychain/src/org/sufficientlysecure/keychain
diff options
context:
space:
mode:
authorDominik Schürmann <dominik@dominikschuermann.de>2013-09-06 13:52:57 +0200
committerDominik Schürmann <dominik@dominikschuermann.de>2013-09-06 13:52:57 +0200
commitde8e1a39d59e1a0f40e173ba3c28e4250d1a48ef (patch)
tree5cbc34684c63888d3e8e775ad35f79d498908cb7 /OpenPGP-Keychain/src/org/sufficientlysecure/keychain
parentb9eaf235621ff59a661b45b20ac1106f42ee4be5 (diff)
parent3a66c1c25aff1dadf4779c2df0fac04f3a9f3c0a (diff)
downloadopen-keychain-de8e1a39d59e1a0f40e173ba3c28e4250d1a48ef.tar.gz
open-keychain-de8e1a39d59e1a0f40e173ba3c28e4250d1a48ef.tar.bz2
open-keychain-de8e1a39d59e1a0f40e173ba3c28e4250d1a48ef.zip
merge k9mail back into master
Diffstat (limited to 'OpenPGP-Keychain/src/org/sufficientlysecure/keychain')
-rw-r--r--OpenPGP-Keychain/src/org/sufficientlysecure/keychain/Id.java1
-rw-r--r--OpenPGP-Keychain/src/org/sufficientlysecure/keychain/crypto_provider/CryptoActivity.java92
-rw-r--r--OpenPGP-Keychain/src/org/sufficientlysecure/keychain/crypto_provider/CryptoService.java196
-rw-r--r--OpenPGP-Keychain/src/org/sufficientlysecure/keychain/crypto_provider/RegisterActivity.java74
-rw-r--r--OpenPGP-Keychain/src/org/sufficientlysecure/keychain/helper/OtherHelper.java49
-rw-r--r--OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainContract.java29
-rw-r--r--OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainDatabase.java20
-rw-r--r--OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainProvider.java120
-rw-r--r--OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/ProviderHelper.java61
-rw-r--r--OpenPGP-Keychain/src/org/sufficientlysecure/keychain/remote_api/AppSettings.java70
-rw-r--r--OpenPGP-Keychain/src/org/sufficientlysecure/keychain/remote_api/AppSettingsActivity.java107
-rw-r--r--OpenPGP-Keychain/src/org/sufficientlysecure/keychain/remote_api/AppSettingsFragment.java208
-rw-r--r--OpenPGP-Keychain/src/org/sufficientlysecure/keychain/remote_api/CryptoService.java409
-rw-r--r--OpenPGP-Keychain/src/org/sufficientlysecure/keychain/remote_api/CryptoServiceActivity.java267
-rw-r--r--OpenPGP-Keychain/src/org/sufficientlysecure/keychain/remote_api/IServiceActivityCallback.aidl26
-rw-r--r--OpenPGP-Keychain/src/org/sufficientlysecure/keychain/remote_api/RegisteredAppsAdapter.java75
-rw-r--r--OpenPGP-Keychain/src/org/sufficientlysecure/keychain/remote_api/RegisteredAppsListActivity.java44
-rw-r--r--OpenPGP-Keychain/src/org/sufficientlysecure/keychain/remote_api/RegisteredAppsListFragment.java90
-rw-r--r--OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/PassphraseCacheService.java54
-rw-r--r--OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/.SelectSecretKeyFragment.java.kate-swpbin0 -> 64 bytes
-rw-r--r--OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/MainActivity.java8
-rw-r--r--OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java13
-rw-r--r--OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/SelectSecretKeyFragment.java2
-rw-r--r--OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/ImportKeysListLoader.java10
-rw-r--r--OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/SelectKeyCursorAdapter.java12
-rw-r--r--OpenPGP-Keychain/src/org/sufficientlysecure/keychain/util/PausableThreadPoolExecutor.java89
26 files changed, 1650 insertions, 476 deletions
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/Id.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/Id.java
index 382f144d7..b0d60cf94 100644
--- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/Id.java
+++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/Id.java
@@ -62,6 +62,7 @@ public final class Id {
public static final int import_from_file = 0x21070020;
public static final int import_from_qr_code = 0x21070021;
public static final int import_from_nfc = 0x21070022;
+ public static final int crypto_consumers = 0x21070023;
}
}
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/crypto_provider/CryptoActivity.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/crypto_provider/CryptoActivity.java
deleted file mode 100644
index b1d248e42..000000000
--- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/crypto_provider/CryptoActivity.java
+++ /dev/null
@@ -1,92 +0,0 @@
-package org.sufficientlysecure.keychain.crypto_provider;
-
-import org.sufficientlysecure.keychain.Constants;
-import org.sufficientlysecure.keychain.Id;
-import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.helper.PgpMain;
-import org.sufficientlysecure.keychain.provider.ProviderHelper;
-import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
-import org.sufficientlysecure.keychain.util.Log;
-
-import com.actionbarsherlock.app.SherlockFragmentActivity;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.os.Messenger;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.Button;
-
-public class CryptoActivity extends SherlockFragmentActivity {
-
- public static final String ACTION_CACHE_PASSPHRASE = "org.sufficientlysecure.keychain.CRYPTO_CACHE_PASSPHRASE";
-
- public static final String EXTRA_SECRET_KEY_ID = "secret_key_id";
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- handleActions(getIntent());
- }
-
- protected void handleActions(Intent intent) {
-
- // TODO: Important: Check if calling package is in list!
-
- String action = intent.getAction();
- Bundle extras = intent.getExtras();
-
- if (extras == null) {
- extras = new Bundle();
- }
-
- /**
- * com.android.crypto actions
- */
- if (ACTION_CACHE_PASSPHRASE.equals(action)) {
- long secretKeyId = extras.getLong(EXTRA_SECRET_KEY_ID);
-
- showPassphraseDialog(secretKeyId);
- } else {
- Log.e(Constants.TAG, "Wrong action!");
- setResult(RESULT_CANCELED);
- finish();
- }
- }
-
- /**
- * Shows passphrase dialog to cache a new passphrase the user enters for using it later for
- * encryption. Based on mSecretKeyId it asks for a passphrase to open a private key or it asks
- * for a symmetric passphrase
- */
- private void showPassphraseDialog(long secretKeyId) {
- // Message is received after passphrase is cached
- Handler returnHandler = new Handler() {
- @Override
- public void handleMessage(Message message) {
- if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
- setResult(RESULT_OK);
- finish();
- }
- }
- };
-
- // Create a new Messenger for the communication back
- Messenger messenger = new Messenger(returnHandler);
-
- try {
- PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance(this,
- messenger, secretKeyId);
-
- passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog");
- } catch (PgpMain.PgpGeneralException e) {
- Log.d(Constants.TAG, "No passphrase for this secret key, encrypt directly!");
- // send message to handler to start encryption directly
- returnHandler.sendEmptyMessage(PassphraseDialogFragment.MESSAGE_OKAY);
- }
- }
-}
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/crypto_provider/CryptoService.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/crypto_provider/CryptoService.java
deleted file mode 100644
index 1a57a457d..000000000
--- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/crypto_provider/CryptoService.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.sufficientlysecure.keychain.crypto_provider;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
-import org.sufficientlysecure.keychain.Constants;
-import org.sufficientlysecure.keychain.Id;
-import org.sufficientlysecure.keychain.helper.PgpMain;
-import org.sufficientlysecure.keychain.util.InputData;
-import org.sufficientlysecure.keychain.util.Log;
-import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.service.KeychainIntentService;
-import org.sufficientlysecure.keychain.service.PassphraseCacheService;
-
-import com.android.crypto.CryptoError;
-import com.android.crypto.ICryptoCallback;
-import com.android.crypto.ICryptoService;
-import com.android.crypto.CryptoSignatureResult;
-
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-
-public class CryptoService extends Service {
- Context mContext;
-
- @Override
- public void onCreate() {
- super.onCreate();
- mContext = this;
- Log.d(Constants.TAG, "CryptoService, onCreate()");
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- Log.d(Constants.TAG, "CryptoService, onDestroy()");
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return mBinder;
- }
-
- private synchronized void decryptAndVerifySafe(byte[] inputBytes, ICryptoCallback callback)
- throws RemoteException {
- try {
- // build InputData and write into OutputStream
- InputStream inputStream = new ByteArrayInputStream(inputBytes);
- long inputLength = inputBytes.length;
- InputData inputData = new InputData(inputStream, inputLength);
-
- OutputStream outputStream = new ByteArrayOutputStream();
-
- long secretKeyId = PgpMain.getDecryptionKeyId(mContext, inputStream);
- if (secretKeyId == Id.key.none) {
- throw new PgpMain.PgpGeneralException(getString(R.string.error_noSecretKeyFound));
- }
-
- Log.d(Constants.TAG, "Got input:\n"+new String(inputBytes));
-
- Log.d(Constants.TAG, "secretKeyId " + secretKeyId);
-
- String passphrase = PassphraseCacheService.getCachedPassphrase(mContext, secretKeyId);
-
- if (passphrase == null) {
- Log.d(Constants.TAG, "No passphrase! Activity required!");
- // No passphrase cached for this ciphertext! Intent required to cache
- // passphrase!
- Intent intent = new Intent(CryptoActivity.ACTION_CACHE_PASSPHRASE);
- intent.putExtra(CryptoActivity.EXTRA_SECRET_KEY_ID, secretKeyId);
- callback.onActivityRequired(intent);
- return;
- }
-
- // if (signedOnly) {
- // resultData = PgpMain.verifyText(this, this, inputData, outStream,
- // lookupUnknownKey);
- // } else {
- // resultData = PgpMain.decryptAndVerify(this, this, inputData, outStream,
- // PassphraseCacheService.getCachedPassphrase(this, secretKeyId),
- // assumeSymmetricEncryption);
- // }
-
- Bundle outputBundle = PgpMain.decryptAndVerify(mContext, null, inputData, outputStream,
- passphrase, false);
-
- outputStream.close();
-
- byte[] outputBytes = ((ByteArrayOutputStream) outputStream).toByteArray();
-
- // get signature informations from bundle
- boolean signature = outputBundle.getBoolean(KeychainIntentService.RESULT_SIGNATURE);
- long signatureKeyId = outputBundle
- .getLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID);
- String signatureUserId = outputBundle
- .getString(KeychainIntentService.RESULT_SIGNATURE_USER_ID);
- boolean signatureSuccess = outputBundle
- .getBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS);
- boolean signatureUnknown = outputBundle
- .getBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN);
-
- CryptoSignatureResult sigResult = new CryptoSignatureResult(signatureUserId, signature,
- signatureSuccess, signatureUnknown);
-
- // return over handler on client side
- callback.onDecryptVerifySuccess(outputBytes, sigResult);
- } catch (Exception e) {
- Log.e(Constants.TAG, "KeychainService, Exception!", e);
-
- try {
- callback.onError(new CryptoError(0, e.getMessage()));
- } catch (Exception t) {
- Log.e(Constants.TAG, "Error returning exception to client", t);
- }
- }
- }
-
- private final ICryptoService.Stub mBinder = new ICryptoService.Stub() {
-
- @Override
- public void encrypt(byte[] inputBytes, String[] encryptionUserIds, ICryptoCallback callback)
- throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void encryptAndSign(byte[] inputBytes, String[] encryptionUserIds,
- String signatureUserId, ICryptoCallback callback) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void sign(byte[] inputBytes, String signatureUserId, ICryptoCallback callback)
- throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void decryptAndVerify(byte[] inputBytes, ICryptoCallback callback)
- throws RemoteException {
- decryptAndVerifySafe(inputBytes, callback);
- }
-
- };
-
- // /**
- // * As we can not throw an exception through Android RPC, we assign identifiers to the
- // exception
- // * types.
- // *
- // * @param e
- // * @return
- // */
- // private int getExceptionId(Exception e) {
- // if (e instanceof NoSuchProviderException) {
- // return 0;
- // } else if (e instanceof NoSuchAlgorithmException) {
- // return 1;
- // } else if (e instanceof SignatureException) {
- // return 2;
- // } else if (e instanceof IOException) {
- // return 3;
- // } else if (e instanceof PgpGeneralException) {
- // return 4;
- // } else if (e instanceof PGPException) {
- // return 5;
- // } else {
- // return -1;
- // }
- // }
-
-}
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/crypto_provider/RegisterActivity.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/crypto_provider/RegisterActivity.java
deleted file mode 100644
index 39b29f9a0..000000000
--- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/crypto_provider/RegisterActivity.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package org.sufficientlysecure.keychain.crypto_provider;
-
-import org.sufficientlysecure.keychain.Constants;
-import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.provider.ProviderHelper;
-import org.sufficientlysecure.keychain.util.Log;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.Button;
-
-public class RegisterActivity extends Activity {
-
- public static final String ACTION_REGISTER = "com.android.crypto.REGISTER";
-
- public static final String EXTRA_PACKAGE_NAME = "packageName";
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- handleActions(getIntent());
- }
-
- protected void handleActions(Intent intent) {
- String action = intent.getAction();
- Bundle extras = intent.getExtras();
-
- if (extras == null) {
- extras = new Bundle();
- }
-
- final String callingPackageName = this.getCallingPackage();
-
- /**
- * com.android.crypto actions
- */
- if (ACTION_REGISTER.equals(action)) {
- setContentView(R.layout.register_crypto_consumer_activity);
-
- Button allowButton = (Button) findViewById(R.id.register_crypto_consumer_allow);
- Button disallowButton = (Button) findViewById(R.id.register_crypto_consumer_disallow);
-
- allowButton.setOnClickListener(new OnClickListener() {
-
- @Override
- public void onClick(View v) {
- ProviderHelper.addCryptoConsumer(RegisterActivity.this, callingPackageName);
- Intent data = new Intent();
- data.putExtra(EXTRA_PACKAGE_NAME, "org.sufficientlysecure.keychain");
-
- setResult(RESULT_OK, data);
- finish();
- }
- });
-
- disallowButton.setOnClickListener(new OnClickListener() {
-
- @Override
- public void onClick(View v) {
- setResult(RESULT_CANCELED);
- finish();
- }
- });
-
- } else {
- Log.e(Constants.TAG, "Please use com.android.crypto.REGISTER as intent action!");
- finish();
- }
- }
-}
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/helper/OtherHelper.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/helper/OtherHelper.java
index 6a71ca0ba..5dc561923 100644
--- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/helper/OtherHelper.java
+++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/helper/OtherHelper.java
@@ -120,28 +120,33 @@ public class OtherHelper {
public static void checkPackagePermissionForActions(Activity activity, String pkgName,
String permName, String action, String[] restrictedActions) {
if (action != null) {
- PackageManager pkgManager = activity.getPackageManager();
-
- for (int i = 0; i < restrictedActions.length; i++) {
- if (restrictedActions[i].equals(action)) {
- if (pkgName != null
- && (pkgManager.checkPermission(permName, pkgName) == PackageManager.PERMISSION_GRANTED || pkgName
- .equals(Constants.PACKAGE_NAME))) {
- Log.d(Constants.TAG, pkgName + " has permission " + permName + ". Action "
- + action + " was granted!");
- } else {
- String error = pkgName + " does NOT have permission " + permName
- + ". Action " + action + " was NOT granted!";
- Log.e(Constants.TAG, error);
- Toast.makeText(activity, activity.getString(R.string.errorMessage, error),
- Toast.LENGTH_LONG).show();
-
- // end activity
- activity.setResult(Activity.RESULT_CANCELED, null);
- activity.finish();
- }
- }
- }
+// PackageManager pkgManager = activity.getPackageManager();
+
+// for (int i = 0; i < restrictedActions.length; i++) {
+// if (restrictedActions[i].equals(action)) {
+// if (pkgName != null
+// && (pkgManager.checkPermission(permName, pkgName) == PackageManager.PERMISSION_GRANTED || pkgName
+// .equals(Constants.PACKAGE_NAME))) {
+// Log.d(Constants.TAG, pkgName + " has permission " + permName + ". Action "
+// + action + " was granted!");
+// } else {
+// String error = pkgName + " does NOT have permission " + permName
+// + ". Action " + action + " was NOT granted!";
+// Log.e(Constants.TAG, error);
+// Toast.makeText(activity, activity.getString(R.string.errorMessage, error),
+// Toast.LENGTH_LONG).show();
+//
+// // end activity
+// activity.setResult(Activity.RESULT_CANCELED, null);
+// activity.finish();
+// }
+// }
+// }
+
+ // TODO: currently always cancels! THis is the old API
+ // end activity
+ activity.setResult(Activity.RESULT_CANCELED, null);
+ activity.finish();
}
}
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainContract.java
index 46928c6fa..93ee50a5e 100644
--- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainContract.java
+++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainContract.java
@@ -43,7 +43,7 @@ public class KeychainContract {
String CREATION = "creation";
String EXPIRY = "expiry";
String KEY_RING_ROW_ID = "key_ring_row_id"; // foreign key to key_rings._ID
- String KEY_DATA = "key_data"; // PGPPublicKey / PGPSecretKey blob
+ String KEY_DATA = "key_data"; // PGPPublicKey/PGPSecretKey blob
String RANK = "rank";
}
@@ -53,8 +53,13 @@ public class KeychainContract {
String RANK = "rank";
}
- interface CryptoConsumersColumns {
+ interface ApiAppsColumns {
String PACKAGE_NAME = "package_name";
+ String KEY_ID = "key_id"; // not a database id
+ String ASCII_ARMOR = "ascii_armor";
+ String ENCRYPTION_ALGORITHM = "encryption_algorithm";
+ String HASH_ALORITHM = "hash_algorithm";
+ String COMPRESSION = "compression";
}
public static final class KeyTypes {
@@ -82,7 +87,8 @@ public class KeychainContract {
public static final String PATH_USER_IDS = "user_ids";
public static final String PATH_KEYS = "keys";
- public static final String BASE_CRYPTO_CONSUMERS = "crypto_consumers";
+ public static final String BASE_API_APPS = "api_apps";
+ public static final String PATH_BY_PACKAGE_NAME = "package_name";
public static class KeyRings implements KeyRingsColumns, BaseColumns {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
@@ -213,15 +219,24 @@ public class KeychainContract {
}
}
- public static class CryptoConsumers implements CryptoConsumersColumns, BaseColumns {
+ public static class ApiApps implements ApiAppsColumns, BaseColumns {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
- .appendPath(BASE_CRYPTO_CONSUMERS).build();
+ .appendPath(BASE_API_APPS).build();
/** Use if multiple items get returned */
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.thialfihar.apg.crypto_consumers";
+ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.thialfihar.apg.api_apps";
/** Use if a single item is returned */
- public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.crypto_consumers";
+ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.api_apps";
+
+ public static Uri buildIdUri(String rowId) {
+ return CONTENT_URI.buildUpon().appendPath(rowId).build();
+ }
+
+ public static Uri buildByPackageNameUri(String packageName) {
+ return CONTENT_URI.buildUpon().appendPath(PATH_BY_PACKAGE_NAME).appendPath(packageName)
+ .build();
+ }
}
public static class DataStream {
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainDatabase.java
index f30292b52..6da96f45f 100644
--- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainDatabase.java
+++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainDatabase.java
@@ -18,7 +18,7 @@
package org.sufficientlysecure.keychain.provider;
import org.sufficientlysecure.keychain.Constants;
-import org.sufficientlysecure.keychain.provider.KeychainContract.CryptoConsumersColumns;
+import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIdsColumns;
@@ -37,7 +37,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
String KEY_RINGS = "key_rings";
String KEYS = "keys";
String USER_IDS = "user_ids";
- String CRYPTO_CONSUMERS = "crypto_consumers";
+ String API_APPS = "api_apps";
}
private static final String CREATE_KEY_RINGS = "CREATE TABLE IF NOT EXISTS " + Tables.KEY_RINGS
@@ -64,10 +64,14 @@ public class KeychainDatabase extends SQLiteOpenHelper {
+ UserIdsColumns.KEY_RING_ROW_ID + ") REFERENCES " + Tables.KEY_RINGS + "("
+ BaseColumns._ID + ") ON DELETE CASCADE)";
- private static final String CREATE_CRYPTO_CONSUMERS = "CREATE TABLE IF NOT EXISTS "
- + Tables.CRYPTO_CONSUMERS + " (" + BaseColumns._ID
- + " INTEGER PRIMARY KEY AUTOINCREMENT, " + CryptoConsumersColumns.PACKAGE_NAME
- + " TEXT UNIQUE)";
+ private static final String CREATE_API_APPS = "CREATE TABLE IF NOT EXISTS "
+ + Tables.API_APPS + " (" + BaseColumns._ID
+ + " INTEGER PRIMARY KEY AUTOINCREMENT, " + ApiAppsColumns.PACKAGE_NAME
+ + " TEXT UNIQUE, " + ApiAppsColumns.KEY_ID + " INT64, "
+ + ApiAppsColumns.ASCII_ARMOR + " INTEGER, "
+ + ApiAppsColumns.ENCRYPTION_ALGORITHM + " INTEGER, "
+ + ApiAppsColumns.HASH_ALORITHM + " INTEGER, "
+ + ApiAppsColumns.COMPRESSION + " INTEGER)";
KeychainDatabase(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
@@ -80,7 +84,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
db.execSQL(CREATE_KEY_RINGS);
db.execSQL(CREATE_KEYS);
db.execSQL(CREATE_USER_IDS);
- db.execSQL(CREATE_CRYPTO_CONSUMERS);
+ db.execSQL(CREATE_API_APPS);
}
@Override
@@ -108,7 +112,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
+ " = 1 WHERE " + KeysColumns.IS_MASTER_KEY + "= 1;");
break;
case 4:
- db.execSQL(CREATE_CRYPTO_CONSUMERS);
+ db.execSQL(CREATE_API_APPS);
default:
break;
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainProvider.java
index 49286b9ce..edb82e632 100644
--- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainProvider.java
+++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,13 +17,11 @@
package org.sufficientlysecure.keychain.provider;
-import java.io.File;
-import java.io.FileNotFoundException;
import java.util.Arrays;
import java.util.HashMap;
import org.sufficientlysecure.keychain.Constants;
-import org.sufficientlysecure.keychain.provider.KeychainContract.CryptoConsumers;
+import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyTypes;
@@ -44,7 +42,6 @@ import android.database.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
-import android.os.ParcelFileDescriptor;
import android.provider.BaseColumns;
import android.text.TextUtils;
@@ -81,7 +78,9 @@ public class KeychainProvider extends ContentProvider {
private static final int SECRET_KEY_RING_USER_ID = 221;
private static final int SECRET_KEY_RING_USER_ID_BY_ROW_ID = 222;
- private static final int CRYPTO_CONSUMERS = 301;
+ private static final int API_APPS = 301;
+ private static final int API_APPS_BY_ROW_ID = 302;
+ private static final int API_APPS_BY_PACKAGE_NAME = 303;
// private static final int DATA_STREAM = 401;
@@ -227,9 +226,12 @@ public class KeychainProvider extends ContentProvider {
SECRET_KEY_RING_USER_ID_BY_ROW_ID);
/**
- * Crypto Consumers
+ * API apps
*/
- matcher.addURI(authority, KeychainContract.BASE_CRYPTO_CONSUMERS, CRYPTO_CONSUMERS);
+ matcher.addURI(authority, KeychainContract.BASE_API_APPS, API_APPS);
+ matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/#", API_APPS_BY_ROW_ID);
+ matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/"
+ + KeychainContract.PATH_BY_PACKAGE_NAME + "/*", API_APPS_BY_PACKAGE_NAME);
/**
* data stream
@@ -290,8 +292,12 @@ public class KeychainProvider extends ContentProvider {
case SECRET_KEY_RING_USER_ID_BY_ROW_ID:
return UserIds.CONTENT_ITEM_TYPE;
- case CRYPTO_CONSUMERS:
- return CryptoConsumers.CONTENT_TYPE;
+ case API_APPS:
+ return ApiApps.CONTENT_TYPE;
+
+ case API_APPS_BY_ROW_ID:
+ case API_APPS_BY_PACKAGE_NAME:
+ return ApiApps.CONTENT_ITEM_TYPE;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
@@ -600,10 +606,23 @@ public class KeychainProvider extends ContentProvider {
qb.appendWhereEscapeString(uri.getLastPathSegment());
break;
-
- case CRYPTO_CONSUMERS:
- qb.setTables(Tables.CRYPTO_CONSUMERS);
-
+
+ case API_APPS:
+ qb.setTables(Tables.API_APPS);
+
+ break;
+ case API_APPS_BY_ROW_ID:
+ qb.setTables(Tables.API_APPS);
+
+ qb.appendWhere(BaseColumns._ID + " = ");
+ qb.appendWhereEscapeString(uri.getLastPathSegment());
+
+ break;
+ case API_APPS_BY_PACKAGE_NAME:
+ qb.setTables(Tables.API_APPS);
+ qb.appendWhere(ApiApps.PACKAGE_NAME + " = ");
+ qb.appendWhereEscapeString(uri.getPathSegments().get(2));
+
break;
default:
@@ -653,6 +672,7 @@ public class KeychainProvider extends ContentProvider {
rowId = db.insertOrThrow(Tables.KEY_RINGS, null, values);
rowUri = KeyRings.buildPublicKeyRingsUri(Long.toString(rowId));
+ sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
break;
case PUBLIC_KEY_RING_KEY:
@@ -660,11 +680,13 @@ public class KeychainProvider extends ContentProvider {
rowId = db.insertOrThrow(Tables.KEYS, null, values);
rowUri = Keys.buildPublicKeysUri(Long.toString(rowId));
+ sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
break;
case PUBLIC_KEY_RING_USER_ID:
rowId = db.insertOrThrow(Tables.USER_IDS, null, values);
rowUri = UserIds.buildPublicUserIdsUri(Long.toString(rowId));
+ sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
break;
case SECRET_KEY_RING:
@@ -672,6 +694,7 @@ public class KeychainProvider extends ContentProvider {
rowId = db.insertOrThrow(Tables.KEY_RINGS, null, values);
rowUri = KeyRings.buildSecretKeyRingsUri(Long.toString(rowId));
+ sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
break;
case SECRET_KEY_RING_KEY:
@@ -679,6 +702,7 @@ public class KeychainProvider extends ContentProvider {
rowId = db.insertOrThrow(Tables.KEYS, null, values);
rowUri = Keys.buildSecretKeysUri(Long.toString(rowId));
+ sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
break;
case SECRET_KEY_RING_USER_ID:
@@ -686,13 +710,17 @@ public class KeychainProvider extends ContentProvider {
rowUri = UserIds.buildSecretUserIdsUri(Long.toString(rowId));
break;
+ case API_APPS:
+ rowId = db.insertOrThrow(Tables.API_APPS, null, values);
+ rowUri = ApiApps.buildIdUri(Long.toString(rowId));
+
+ break;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
// notify of changes in db
getContext().getContentResolver().notifyChange(uri, null);
- sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
} catch (SQLiteConstraintException e) {
Log.e(Constants.TAG, "Constraint exception on insert! Entry already existing?");
@@ -720,6 +748,7 @@ public class KeychainProvider extends ContentProvider {
count = db.delete(Tables.KEY_RINGS,
buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match), selection),
selectionArgs);
+ sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
break;
case PUBLIC_KEY_RING_BY_MASTER_KEY_ID:
case SECRET_KEY_RING_BY_MASTER_KEY_ID:
@@ -728,24 +757,33 @@ public class KeychainProvider extends ContentProvider {
count = db.delete(Tables.KEY_RINGS,
buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match), selection),
selectionArgs);
+ sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
break;
case PUBLIC_KEY_RING_KEY_BY_ROW_ID:
case SECRET_KEY_RING_KEY_BY_ROW_ID:
count = db.delete(Tables.KEYS,
buildDefaultKeysSelection(uri, getKeyType(match), selection), selectionArgs);
+ sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
break;
case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID:
case SECRET_KEY_RING_USER_ID_BY_ROW_ID:
count = db.delete(Tables.KEYS, buildDefaultUserIdsSelection(uri, selection),
selectionArgs);
break;
+ case API_APPS_BY_ROW_ID:
+ count = db.delete(Tables.API_APPS, buildDefaultApiAppsSelection(uri, false, selection),
+ selectionArgs);
+ break;
+ case API_APPS_BY_PACKAGE_NAME:
+ count = db.delete(Tables.API_APPS, buildDefaultApiAppsSelection(uri, true, selection),
+ selectionArgs);
+ break;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
// notify of changes in db
getContext().getContentResolver().notifyChange(uri, null);
- sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
return count;
}
@@ -771,6 +809,8 @@ public class KeychainProvider extends ContentProvider {
values,
buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match),
selection), selectionArgs);
+ sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
+
break;
case PUBLIC_KEY_RING_BY_MASTER_KEY_ID:
case SECRET_KEY_RING_BY_MASTER_KEY_ID:
@@ -781,6 +821,8 @@ public class KeychainProvider extends ContentProvider {
values,
buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match),
selection), selectionArgs);
+ sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
+
break;
case PUBLIC_KEY_RING_KEY_BY_ROW_ID:
case SECRET_KEY_RING_KEY_BY_ROW_ID:
@@ -788,19 +830,28 @@ public class KeychainProvider extends ContentProvider {
.update(Tables.KEYS, values,
buildDefaultKeysSelection(uri, getKeyType(match), selection),
selectionArgs);
+ sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
+
break;
case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID:
case SECRET_KEY_RING_USER_ID_BY_ROW_ID:
count = db.update(Tables.USER_IDS, values,
buildDefaultUserIdsSelection(uri, selection), selectionArgs);
break;
+ case API_APPS_BY_ROW_ID:
+ count = db.update(Tables.API_APPS, values,
+ buildDefaultApiAppsSelection(uri, false, selection), selectionArgs);
+ break;
+ case API_APPS_BY_PACKAGE_NAME:
+ count = db.update(Tables.API_APPS, values,
+ buildDefaultApiAppsSelection(uri, true, selection), selectionArgs);
+ break;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
// notify of changes in db
getContext().getContentResolver().notifyChange(uri, null);
- sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
} catch (SQLiteConstraintException e) {
Log.e(Constants.TAG, "Constraint exception on update! Entry already existing?");
@@ -883,6 +934,29 @@ public class KeychainProvider extends ContentProvider {
return BaseColumns._ID + "=" + rowId + andForeignKeyRing + andSelection;
}
+ /**
+ * Build default selection statement for API apps. If no extra selection is specified only build
+ * where clause with rowId
+ *
+ * @param uri
+ * @param selection
+ * @return
+ */
+ private String buildDefaultApiAppsSelection(Uri uri, boolean packageSelection, String selection) {
+ String lastPathSegment = uri.getLastPathSegment();
+
+ String andSelection = "";
+ if (!TextUtils.isEmpty(selection)) {
+ andSelection = " AND (" + selection + ")";
+ }
+
+ if (packageSelection) {
+ return ApiApps.PACKAGE_NAME + "=" + lastPathSegment + andSelection;
+ } else {
+ return BaseColumns._ID + "=" + lastPathSegment + andSelection;
+ }
+ }
+
// @Override
// public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
// int match = mUriMatcher.match(uri);
@@ -899,10 +973,12 @@ public class KeychainProvider extends ContentProvider {
* updated, or deleted
*/
private void sendBroadcastDatabaseChange(int keyType, String contentItemType) {
- Intent intent = new Intent();
- intent.setAction(ACTION_BROADCAST_DATABASE_CHANGE);
- intent.putExtra(EXTRA_BROADCAST_KEY_TYPE, keyType);
- intent.putExtra(EXTRA_BROADCAST_CONTENT_ITEM_TYPE, contentItemType);
- getContext().sendBroadcast(intent, Constants.PERMISSION_ACCESS_API);
+ // TODO: Disabled, old API
+ // Intent intent = new Intent();
+ // intent.setAction(ACTION_BROADCAST_DATABASE_CHANGE);
+ // intent.putExtra(EXTRA_BROADCAST_KEY_TYPE, keyType);
+ // intent.putExtra(EXTRA_BROADCAST_CONTENT_ITEM_TYPE, contentItemType);
+ //
+ // getContext().sendBroadcast(intent, Constants.PERMISSION_ACCESS_API);
}
}
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/ProviderHelper.java
index 57d3b54d6..d0fcfe999 100644
--- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/ProviderHelper.java
+++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/ProviderHelper.java
@@ -31,11 +31,12 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.helper.PgpConversionHelper;
import org.sufficientlysecure.keychain.helper.PgpHelper;
import org.sufficientlysecure.keychain.helper.PgpMain;
-import org.sufficientlysecure.keychain.provider.KeychainContract.CryptoConsumers;
+import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
+import org.sufficientlysecure.keychain.remote_api.AppSettings;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
@@ -718,13 +719,13 @@ public class ProviderHelper {
return cursor;
}
- public static ArrayList<String> getCryptoConsumers(Context context) {
- Cursor cursor = context.getContentResolver().query(CryptoConsumers.CONTENT_URI, null, null,
- null, null);
+ public static ArrayList<String> getRegisteredApiApps(Context context) {
+ Cursor cursor = context.getContentResolver().query(ApiApps.CONTENT_URI, null, null, null,
+ null);
ArrayList<String> packageNames = new ArrayList<String>();
if (cursor != null) {
- int packageNameCol = cursor.getColumnIndex(CryptoConsumers.PACKAGE_NAME);
+ int packageNameCol = cursor.getColumnIndex(ApiApps.PACKAGE_NAME);
if (cursor.moveToFirst()) {
do {
packageNames.add(cursor.getString(packageNameCol));
@@ -739,9 +740,53 @@ public class ProviderHelper {
return packageNames;
}
- public static void addCryptoConsumer(Context context, String packageName) {
+ private static void contentValueForApiApps() {
+
+ }
+
+ public static void insertApiApp(Context context, AppSettings appSettings) {
ContentValues values = new ContentValues();
- values.put(CryptoConsumers.PACKAGE_NAME, packageName);
- context.getContentResolver().insert(CryptoConsumers.CONTENT_URI, values);
+ values.put(ApiApps.PACKAGE_NAME, appSettings.getPackageName());
+ values.put(ApiApps.KEY_ID, appSettings.getKeyId());
+ values.put(ApiApps.ASCII_ARMOR, appSettings.isAsciiArmor());
+ // TODO: other parameters
+ context.getContentResolver().insert(ApiApps.CONTENT_URI, values);
+ }
+
+ public static void updateApiApp(Context context, AppSettings appSettings, Uri uri) {
+ final ContentValues cv = new ContentValues();
+ cv.put(KeychainContract.ApiApps.KEY_ID, appSettings.getKeyId());
+
+ cv.put(KeychainContract.ApiApps.ASCII_ARMOR, appSettings.isAsciiArmor());
+ // TODO: other parameters
+
+ if (context.getContentResolver().update(uri, cv, null, null) <= 0) {
+ throw new RuntimeException();
+ }
+ }
+
+ public static AppSettings getApiAppSettings(Context context, Uri uri) {
+ AppSettings settings = new AppSettings();
+ Cursor cur = context.getContentResolver().query(uri, null, null, null, null);
+ if (cur == null) {
+ return null;
+ }
+ if (cur.moveToFirst()) {
+ settings.setPackageName(cur.getString(cur
+ .getColumnIndex(KeychainContract.ApiApps.PACKAGE_NAME)));
+
+ settings.setKeyId(cur.getLong(cur.getColumnIndex(KeychainContract.ApiApps.KEY_ID)));
+
+ settings.setAsciiArmor(cur.getInt(cur
+ .getColumnIndexOrThrow(KeychainContract.ApiApps.ASCII_ARMOR)) == 1);
+
+ settings.setPackageName(cur.getString(cur
+ .getColumnIndex(KeychainContract.ApiApps.PACKAGE_NAME)));
+
+ settings.setPackageName(cur.getString(cur
+ .getColumnIndex(KeychainContract.ApiApps.PACKAGE_NAME)));
+ }
+
+ return settings;
}
}
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/remote_api/AppSettings.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/remote_api/AppSettings.java
new file mode 100644
index 000000000..fe251a5e7
--- /dev/null
+++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/remote_api/AppSettings.java
@@ -0,0 +1,70 @@
+package org.sufficientlysecure.keychain.remote_api;
+
+import org.sufficientlysecure.keychain.Id;
+
+public class AppSettings {
+ private String packageName;
+ private long keyId = Id.key.none;
+ private boolean asciiArmor;
+ private int encryptionAlgorithm = 7; // AES-128
+ private int hashAlgorithm = 10; // SHA-512
+ private int compression = 2; // zlib
+
+ public AppSettings() {
+
+ }
+
+ public AppSettings(String packageName) {
+ super();
+ this.packageName = packageName;
+ }
+
+ public String getPackageName() {
+ return packageName;
+ }
+
+ public void setPackageName(String packageName) {
+ this.packageName = packageName;
+ }
+
+ public long getKeyId() {
+ return keyId;
+ }
+
+ public void setKeyId(long scretKeyId) {
+ this.keyId = scretKeyId;
+ }
+
+ public boolean isAsciiArmor() {
+ return asciiArmor;
+ }
+
+ public void setAsciiArmor(boolean asciiArmor) {
+ this.asciiArmor = asciiArmor;
+ }
+
+ public int getEncryptionAlgorithm() {
+ return encryptionAlgorithm;
+ }
+
+ public void setEncryptionAlgorithm(int encryptionAlgorithm) {
+ this.encryptionAlgorithm = encryptionAlgorithm;
+ }
+
+ public int getHashAlgorithm() {
+ return hashAlgorithm;
+ }
+
+ public void setHashAlgorithm(int hashAlgorithm) {
+ this.hashAlgorithm = hashAlgorithm;
+ }
+
+ public int getCompression() {
+ return compression;
+ }
+
+ public void setCompression(int compression) {
+ this.compression = compression;
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/remote_api/AppSettingsActivity.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/remote_api/AppSettingsActivity.java
new file mode 100644
index 000000000..155369601
--- /dev/null
+++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/remote_api/AppSettingsActivity.java
@@ -0,0 +1,107 @@
+package org.sufficientlysecure.keychain.remote_api;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.util.Log;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TextView;
+
+import com.actionbarsherlock.app.ActionBar;
+import com.actionbarsherlock.app.SherlockFragmentActivity;
+import com.actionbarsherlock.view.Menu;
+import com.actionbarsherlock.view.MenuItem;
+
+public class AppSettingsActivity extends SherlockFragmentActivity {
+ private Uri mAppUri;
+
+ private AppSettingsFragment settingsFragment;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Inflate a "Done" custom action bar view to serve as the "Up" affordance.
+ final LayoutInflater inflater = (LayoutInflater) getSupportActionBar().getThemedContext()
+ .getSystemService(LAYOUT_INFLATER_SERVICE);
+ final View customActionBarView = inflater
+ .inflate(R.layout.actionbar_custom_view_done, null);
+
+ ((TextView) customActionBarView.findViewById(R.id.actionbar_done_text))
+ .setText(R.string.api_settings_save);
+ customActionBarView.findViewById(R.id.actionbar_done).setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // "Done"
+ save();
+ }
+ });
+
+ // Show the custom action bar view and hide the normal Home icon and title.
+ final ActionBar actionBar = getSupportActionBar();
+ actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM, ActionBar.DISPLAY_SHOW_CUSTOM
+ | ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_TITLE);
+ actionBar.setCustomView(customActionBarView);
+
+ setContentView(R.layout.api_app_settings_activity);
+
+ settingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById(
+ R.id.api_app_settings_fragment);
+
+ Intent intent = getIntent();
+ mAppUri = intent.getData();
+ if (mAppUri == null) {
+ Log.e(Constants.TAG, "Intent data missing. Should be Uri of app!");
+ finish();
+ return;
+ } else {
+ Log.d(Constants.TAG, "uri: " + mAppUri);
+ loadData(mAppUri);
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ getSupportMenuInflater().inflate(R.menu.api_app_settings, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_api_settings_revoke:
+ revokeAccess();
+ return true;
+ case R.id.menu_api_settings_cancel:
+ finish();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void loadData(Uri appUri) {
+ AppSettings settings = ProviderHelper.getApiAppSettings(this, appUri);
+ settingsFragment.setAppSettings(settings);
+ }
+
+ private void revokeAccess() {
+ if (getContentResolver().delete(mAppUri, null, null) <= 0) {
+ throw new RuntimeException();
+ }
+ finish();
+ }
+
+ private void save() {
+ ProviderHelper.updateApiApp(this, settingsFragment.getAppSettings(), mAppUri);
+
+ finish();
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/remote_api/AppSettingsFragment.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/remote_api/AppSettingsFragment.java
new file mode 100644
index 000000000..1bb447a7d
--- /dev/null
+++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/remote_api/AppSettingsFragment.java
@@ -0,0 +1,208 @@
+package org.sufficientlysecure.keychain.remote_api;
+
+import org.spongycastle.openpgp.PGPSecretKey;
+import org.spongycastle.openpgp.PGPSecretKeyRing;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.helper.PgpHelper;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.ui.SelectSecretKeyActivity;
+import org.sufficientlysecure.keychain.util.Log;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+public class AppSettingsFragment extends Fragment {
+
+ // model
+ private AppSettings appSettings;
+
+ // view
+ private LinearLayout mAdvancedSettingsContainer;
+ private Button mAdvancedSettingsButton;
+ private TextView mAppNameView;
+ private ImageView mAppIconView;
+ private TextView mKeyUserId;
+ private TextView mKeyUserIdRest;
+ private Button mSelectKeyButton;
+ private CheckBox mAsciiArmorCheckBox;
+
+ public AppSettings getAppSettings() {
+ return appSettings;
+ }
+
+ public void setAppSettings(AppSettings appSettings) {
+ this.appSettings = appSettings;
+ setPackage(appSettings.getPackageName());
+ updateSelectedKeyView(appSettings.getKeyId());
+ mAsciiArmorCheckBox.setChecked(appSettings.isAsciiArmor());
+ }
+
+ /**
+ * Inflate the layout for this fragment
+ */
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.api_app_settings_fragment, container, false);
+ initView(view);
+ return view;
+ }
+
+ private void initView(View view) {
+ mAdvancedSettingsButton = (Button) view.findViewById(R.id.api_app_settings_advanced_button);
+ mAdvancedSettingsContainer = (LinearLayout) view
+ .findViewById(R.id.api_app_settings_advanced);
+
+ mAppNameView = (TextView) view.findViewById(R.id.api_app_settings_app_name);
+ mAppIconView = (ImageView) view.findViewById(R.id.api_app_settings_app_icon);
+ mKeyUserId = (TextView) view.findViewById(R.id.api_app_settings_user_id);
+ mKeyUserIdRest = (TextView) view.findViewById(R.id.api_app_settings_user_id_rest);
+ mSelectKeyButton = (Button) view.findViewById(R.id.api_app_settings_select_key_button);
+ mAsciiArmorCheckBox = (CheckBox) view.findViewById(R.id.api_app_ascii_armor);
+
+ mSelectKeyButton.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ selectSecretKey();
+ }
+ });
+
+ mAsciiArmorCheckBox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
+
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ appSettings.setAsciiArmor(isChecked);
+ }
+ });
+
+ final Animation visibleAnimation = new AlphaAnimation(0.0f, 1.0f);
+ visibleAnimation.setDuration(250);
+ final Animation invisibleAnimation = new AlphaAnimation(1.0f, 0.0f);
+ invisibleAnimation.setDuration(250);
+
+ // TODO: Better: collapse/expand animation
+ // final Animation animation2 = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f,
+ // Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, -1.0f,
+ // Animation.RELATIVE_TO_SELF, 0.0f);
+ // animation2.setDuration(150);
+
+ mAdvancedSettingsButton.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ if (mAdvancedSettingsContainer.getVisibility() == View.VISIBLE) {
+ mAdvancedSettingsContainer.startAnimation(invisibleAnimation);
+ mAdvancedSettingsContainer.setVisibility(View.INVISIBLE);
+ mAdvancedSettingsButton.setText(R.string.api_settings_show_advanced);
+ } else {
+ mAdvancedSettingsContainer.startAnimation(visibleAnimation);
+ mAdvancedSettingsContainer.setVisibility(View.VISIBLE);
+ mAdvancedSettingsButton.setText(R.string.api_settings_hide_advanced);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ }
+
+ private void selectSecretKey() {
+ Intent intent = new Intent(getActivity(), SelectSecretKeyActivity.class);
+ startActivityForResult(intent, Id.request.secret_keys);
+ }
+
+ private void setPackage(String packageName) {
+ PackageManager pm = getActivity().getApplicationContext().getPackageManager();
+
+ // get application name and icon from package manager
+ String appName = null;
+ Drawable appIcon = null;
+ try {
+ ApplicationInfo ai = pm.getApplicationInfo(packageName, 0);
+
+ appName = (String) pm.getApplicationLabel(ai);
+ appIcon = pm.getApplicationIcon(ai);
+ } catch (final NameNotFoundException e) {
+ // fallback
+ appName = packageName;
+ }
+ mAppNameView.setText(appName);
+ mAppIconView.setImageDrawable(appIcon);
+ }
+
+ private void updateSelectedKeyView(long secretKeyId) {
+ if (secretKeyId == Id.key.none) {
+ mKeyUserId.setText(R.string.api_settings_no_key);
+ mKeyUserIdRest.setText("");
+ } else {
+ String uid = getResources().getString(R.string.unknownUserId);
+ String uidExtra = "";
+ PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(
+ getActivity(), secretKeyId);
+ if (keyRing != null) {
+ PGPSecretKey key = PgpHelper.getMasterKey(keyRing);
+ if (key != null) {
+ String userId = PgpHelper.getMainUserIdSafe(getActivity(), key);
+ String chunks[] = userId.split(" <", 2);
+ uid = chunks[0];
+ if (chunks.length > 1) {
+ uidExtra = "<" + chunks[1];
+ }
+ }
+ }
+ mKeyUserId.setText(uid);
+ mKeyUserIdRest.setText(uidExtra);
+ }
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ Log.d(Constants.TAG, "onactivityresult " + requestCode + " " + resultCode);
+ switch (requestCode) {
+
+ case Id.request.secret_keys: {
+ long secretKeyId;
+ if (resultCode == Activity.RESULT_OK) {
+ Bundle bundle = data.getExtras();
+ secretKeyId = bundle.getLong(SelectSecretKeyActivity.RESULT_EXTRA_MASTER_KEY_ID);
+
+ } else {
+ secretKeyId = Id.key.none;
+ }
+ appSettings.setKeyId(secretKeyId);
+ updateSelectedKeyView(secretKeyId);
+ break;
+ }
+
+ default: {
+ break;
+ }
+ }
+
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/remote_api/CryptoService.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/remote_api/CryptoService.java
new file mode 100644
index 000000000..125967c66
--- /dev/null
+++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/remote_api/CryptoService.java
@@ -0,0 +1,409 @@
+/*
+ * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.sufficientlysecure.keychain.remote_api;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.prefs.Preferences;
+
+import org.openintents.crypto.CryptoError;
+import org.openintents.crypto.CryptoSignatureResult;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.helper.PgpMain;
+import org.sufficientlysecure.keychain.util.InputData;
+import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.provider.KeychainContract;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.remote_api.IServiceActivityCallback;
+import org.sufficientlysecure.keychain.service.KeychainIntentService;
+import org.sufficientlysecure.keychain.service.PassphraseCacheService;
+import org.sufficientlysecure.keychain.util.PausableThreadPoolExecutor;
+import org.openintents.crypto.ICryptoCallback;
+import org.openintents.crypto.ICryptoService;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+public class CryptoService extends Service {
+ Context mContext;
+
+ // just one pool of 4 threads, pause on every user action needed
+ final ArrayBlockingQueue<Runnable> mPoolQueue = new ArrayBlockingQueue<Runnable>(20);
+ PausableThreadPoolExecutor mThreadPool = new PausableThreadPoolExecutor(2, 4, 10,
+ TimeUnit.SECONDS, mPoolQueue);
+
+ public static final String ACTION_SERVICE_ACTIVITY = "org.sufficientlysecure.keychain.crypto_provider.IServiceActivityCallback";
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mContext = this;
+ Log.d(Constants.TAG, "CryptoService, onCreate()");
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ Log.d(Constants.TAG, "CryptoService, onDestroy()");
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ // return different binder for connections from internal service activity
+ if (ACTION_SERVICE_ACTIVITY.equals(intent.getAction())) {
+
+ // this binder can only be used from OpenPGP Keychain
+ if (isCallerAllowed(true)) {
+ return mBinderServiceActivity;
+ } else {
+ Log.e(Constants.TAG, "This binder can only be used from " + Constants.PACKAGE_NAME);
+ return null;
+ }
+ } else {
+ return mBinder;
+ }
+ }
+
+ private String getCachedPassphrase(long keyId) {
+ String passphrase = PassphraseCacheService.getCachedPassphrase(mContext, keyId);
+
+ if (passphrase == null) {
+ Log.d(Constants.TAG, "No passphrase! Activity required!");
+
+ // start passphrase dialog
+ Bundle extras = new Bundle();
+ extras.putLong(CryptoServiceActivity.EXTRA_SECRET_KEY_ID, keyId);
+ pauseQueueAndStartServiceActivity(CryptoServiceActivity.ACTION_CACHE_PASSPHRASE, extras);
+ }
+
+ return passphrase;
+ }
+
+ private synchronized void encryptSafe(byte[] inputBytes, String[] encryptionUserIds,
+ AppSettings appSettings, ICryptoCallback callback) throws RemoteException {
+ try {
+ // build InputData and write into OutputStream
+ InputStream inputStream = new ByteArrayInputStream(inputBytes);
+ long inputLength = inputBytes.length;
+ InputData inputData = new InputData(inputStream, inputLength);
+
+ OutputStream outputStream = new ByteArrayOutputStream();
+
+ String passphrase = getCachedPassphrase(appSettings.getKeyId());
+
+ PgpMain.encryptAndSign(mContext, null, inputData, outputStream,
+ appSettings.isAsciiArmor(), appSettings.getCompression(), new long[] {},
+ "test", appSettings.getEncryptionAlgorithm(), Id.key.none,
+ appSettings.getHashAlgorithm(), true, passphrase);
+
+ // PgpMain.encryptAndSign(this, this, inputData, outputStream,
+ // appSettings.isAsciiArmor(),
+ // appSettings.getCompression(), encryptionKeyIds, encryptionPassphrase,
+ // appSettings.getEncryptionAlgorithm(), appSettings.getKeyId(),
+ // appSettings.getHashAlgorithm(), true, passphrase);
+
+ outputStream.close();
+
+ byte[] outputBytes = ((ByteArrayOutputStream) outputStream).toByteArray();
+
+ // return over handler on client side
+ callback.onSuccess(outputBytes, null);
+ } catch (Exception e) {
+ Log.e(Constants.TAG, "KeychainService, Exception!", e);
+
+ try {
+ callback.onError(new CryptoError(0, e.getMessage()));
+ } catch (Exception t) {
+ Log.e(Constants.TAG, "Error returning exception to client", t);
+ }
+ }
+ }
+
+ private synchronized void decryptAndVerifySafe(byte[] inputBytes, ICryptoCallback callback)
+ throws RemoteException {
+ try {
+ // build InputData and write into OutputStream
+ InputStream inputStream = new ByteArrayInputStream(inputBytes);
+ long inputLength = inputBytes.length;
+ InputData inputData = new InputData(inputStream, inputLength);
+
+ OutputStream outputStream = new ByteArrayOutputStream();
+
+ // TODO: This allows to decrypt messages with ALL secret keys, not only the one for the
+ // app, Fix this?
+ long secretKeyId = PgpMain.getDecryptionKeyId(mContext, inputStream);
+ if (secretKeyId == Id.key.none) {
+ throw new PgpMain.PgpGeneralException(getString(R.string.error_noSecretKeyFound));
+ }
+
+ Log.d(Constants.TAG, "Got input:\n" + new String(inputBytes));
+
+ Log.d(Constants.TAG, "secretKeyId " + secretKeyId);
+
+ String passphrase = getCachedPassphrase(secretKeyId);
+
+ // if (signedOnly) {
+ // resultData = PgpMain.verifyText(this, this, inputData, outStream,
+ // lookupUnknownKey);
+ // } else {
+ // resultData = PgpMain.decryptAndVerify(this, this, inputData, outStream,
+ // PassphraseCacheService.getCachedPassphrase(this, secretKeyId),
+ // assumeSymmetricEncryption);
+ // }
+
+ Bundle outputBundle = PgpMain.decryptAndVerify(mContext, null, inputData, outputStream,
+ passphrase, false);
+
+ outputStream.close();
+
+ byte[] outputBytes = ((ByteArrayOutputStream) outputStream).toByteArray();
+
+ // get signature informations from bundle
+ boolean signature = outputBundle.getBoolean(KeychainIntentService.RESULT_SIGNATURE);
+ long signatureKeyId = outputBundle
+ .getLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID);
+ String signatureUserId = outputBundle
+ .getString(KeychainIntentService.RESULT_SIGNATURE_USER_ID);
+ boolean signatureSuccess = outputBundle
+ .getBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS);
+ boolean signatureUnknown = outputBundle
+ .getBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN);
+
+ CryptoSignatureResult sigResult = new CryptoSignatureResult(signatureUserId, signature,
+ signatureSuccess, signatureUnknown);
+
+ // return over handler on client side
+ callback.onSuccess(outputBytes, sigResult);
+ } catch (Exception e) {
+ Log.e(Constants.TAG, "KeychainService, Exception!", e);
+
+ try {
+ callback.onError(new CryptoError(0, e.getMessage()));
+ } catch (Exception t) {
+ Log.e(Constants.TAG, "Error returning exception to client", t);
+ }
+ }
+ }
+
+ private final ICryptoService.Stub mBinder = new ICryptoService.Stub() {
+
+ @Override
+ public void encrypt(final byte[] inputBytes, final String[] encryptionUserIds,
+ final ICryptoCallback callback) throws RemoteException {
+
+ final AppSettings settings = getAppSettings();
+
+ Runnable r = new Runnable() {
+
+ @Override
+ public void run() {
+ try {
+ encryptSafe(inputBytes, encryptionUserIds, settings, callback);
+ } catch (RemoteException e) {
+ Log.e(Constants.TAG, "CryptoService", e);
+ }
+ }
+ };
+
+ checkAndEnqueue(r);
+ }
+
+ @Override
+ public void encryptAndSign(byte[] inputBytes, String[] encryptionUserIds,
+ String signatureUserId, ICryptoCallback callback) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void sign(byte[] inputBytes, String signatureUserId, ICryptoCallback callback)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void decryptAndVerify(final byte[] inputBytes, final ICryptoCallback callback)
+ throws RemoteException {
+
+ Runnable r = new Runnable() {
+
+ @Override
+ public void run() {
+ try {
+ decryptAndVerifySafe(inputBytes, callback);
+ } catch (RemoteException e) {
+ Log.e(Constants.TAG, "CryptoService", e);
+ }
+ }
+ };
+
+ checkAndEnqueue(r);
+ }
+
+ @Override
+ public void setup(boolean asciiArmor, boolean newKeyring, String newKeyringUserId)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ };
+
+ private final IServiceActivityCallback.Stub mBinderServiceActivity = new IServiceActivityCallback.Stub() {
+
+ @Override
+ public void onRegistered(boolean success, String packageName) throws RemoteException {
+
+ if (success) {
+ // resume threads
+ if (isPackageAllowed(packageName, false)) {
+ mThreadPool.resume();
+ } else {
+ // TODO: should not happen?
+ }
+ } else {
+ // TODO
+ mPoolQueue.clear();
+ }
+
+ }
+
+ @Override
+ public void onCachedPassphrase(boolean success) throws RemoteException {
+
+ }
+
+ };
+
+ private void checkAndEnqueue(Runnable r) {
+ if (isCallerAllowed(false)) {
+ mThreadPool.execute(r);
+
+ Log.d(Constants.TAG, "Enqueued runnable…");
+ } else {
+ String[] callingPackages = getPackageManager()
+ .getPackagesForUid(Binder.getCallingUid());
+
+ Log.e(Constants.TAG, "Not allowed to use service! Starting activity for registration!");
+ Bundle extras = new Bundle();
+ // TODO: currently simply uses first entry
+ extras.putString(CryptoServiceActivity.EXTRA_PACKAGE_NAME, callingPackages[0]);
+ pauseQueueAndStartServiceActivity(CryptoServiceActivity.ACTION_REGISTER, extras);
+
+ mThreadPool.execute(r);
+
+ Log.d(Constants.TAG, "Enqueued runnable…");
+ }
+ }
+
+ /**
+ * 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.
+ *
+ * @param allowOnlySelf
+ * allow only Keychain app itself
+ * @return true if process is allowed to use this service
+ */
+ private boolean isCallerAllowed(boolean allowOnlySelf) {
+ String[] callingPackages = getPackageManager().getPackagesForUid(Binder.getCallingUid());
+
+ // is calling package allowed to use this service?
+ for (int i = 0; i < callingPackages.length; i++) {
+ String currentPkg = callingPackages[i];
+
+ if (isPackageAllowed(currentPkg, allowOnlySelf)) {
+ return true;
+ }
+ }
+
+ Log.d(Constants.TAG, "Caller is NOT allowed!");
+ return false;
+ }
+
+ private AppSettings getAppSettings() {
+ String[] callingPackages = getPackageManager().getPackagesForUid(Binder.getCallingUid());
+
+ // is calling package allowed to use this service?
+ for (int i = 0; i < callingPackages.length; i++) {
+ String currentPkg = callingPackages[i];
+
+ Uri uri = KeychainContract.ApiApps.buildByPackageNameUri(currentPkg);
+
+ AppSettings settings = ProviderHelper.getApiAppSettings(this, uri);
+
+ return settings;
+ }
+
+ return null;
+ }
+
+ /**
+ * Checks if packageName is a registered app for the API.
+ *
+ * @param packageName
+ * @param allowOnlySelf
+ * allow only Keychain app itself
+ * @return
+ */
+ private boolean isPackageAllowed(String packageName, boolean allowOnlySelf) {
+ Log.d(Constants.TAG, "packageName: " + packageName);
+
+ ArrayList<String> allowedPkgs = ProviderHelper.getRegisteredApiApps(mContext);
+ Log.d(Constants.TAG, "allowed: " + allowedPkgs);
+
+ // check if package is allowed to use our service
+ if (allowedPkgs.contains(packageName) && (!allowOnlySelf)) {
+ Log.d(Constants.TAG, "Package is allowed! packageName: " + packageName);
+
+ return true;
+ } else if (Constants.PACKAGE_NAME.equals(packageName)) {
+ Log.d(Constants.TAG, "Package is OpenPGP Keychain! -> allowed!");
+
+ return true;
+ }
+
+ return false;
+ }
+
+ private void pauseQueueAndStartServiceActivity(String action, Bundle extras) {
+ mThreadPool.pause();
+
+ Log.d(Constants.TAG, "starting activity...");
+ Intent intent = new Intent(getBaseContext(), CryptoServiceActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setAction(action);
+ if (extras != null) {
+ intent.putExtras(extras);
+ }
+ getApplication().startActivity(intent);
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/remote_api/CryptoServiceActivity.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/remote_api/CryptoServiceActivity.java
new file mode 100644
index 000000000..06c64559a
--- /dev/null
+++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/remote_api/CryptoServiceActivity.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.sufficientlysecure.keychain.remote_api;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.helper.PgpMain;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
+import org.sufficientlysecure.keychain.util.Log;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.actionbarsherlock.app.ActionBar;
+import com.actionbarsherlock.app.SherlockFragmentActivity;
+
+public class CryptoServiceActivity extends SherlockFragmentActivity {
+
+ public static final String ACTION_REGISTER = "org.sufficientlysecure.keychain.remote_api.REGISTER";
+ public static final String ACTION_CACHE_PASSPHRASE = "org.sufficientlysecure.keychain.remote_api.CRYPTO_CACHE_PASSPHRASE";
+
+ public static final String EXTRA_SECRET_KEY_ID = "secretKeyId";
+ public static final String EXTRA_PACKAGE_NAME = "packageName";
+
+ private IServiceActivityCallback mServiceCallback;
+ private boolean mServiceBound;
+
+ // view
+ AppSettingsFragment settingsFragment;
+
+ private ServiceConnection mServiceActivityConnection = new ServiceConnection() {
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mServiceCallback = IServiceActivityCallback.Stub.asInterface(service);
+ Log.d(Constants.TAG, "connected to ICryptoServiceActivity");
+ mServiceBound = true;
+ }
+
+ public void onServiceDisconnected(ComponentName name) {
+ mServiceCallback = null;
+ Log.d(Constants.TAG, "disconnected from ICryptoServiceActivity");
+ mServiceBound = false;
+ }
+ };
+
+ /**
+ * If not already bound, bind!
+ *
+ * @return
+ */
+ public boolean bindToService() {
+ if (mServiceCallback == null && !mServiceBound) { // if not already connected
+ try {
+ Log.d(Constants.TAG, "not bound yet");
+
+ Intent serviceIntent = new Intent();
+ serviceIntent
+ .setAction("org.sufficientlysecure.keychain.crypto_provider.IServiceActivityCallback");
+ bindService(serviceIntent, mServiceActivityConnection, Context.BIND_AUTO_CREATE);
+
+ return true;
+ } catch (Exception e) {
+ Log.d(Constants.TAG, "Exception", e);
+ return false;
+ }
+ } else { // already connected
+ Log.d(Constants.TAG, "already bound... ");
+ return true;
+ }
+ }
+
+ public void unbindFromService() {
+ unbindService(mServiceActivityConnection);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Log.d(Constants.TAG, "onCreate…");
+
+ // bind to our own crypto service
+ bindToService();
+
+ handleActions(getIntent());
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ // unbind from our crypto service
+ if (mServiceActivityConnection != null) {
+ unbindFromService();
+ }
+ }
+
+ protected void handleActions(Intent intent) {
+ String action = intent.getAction();
+ Bundle extras = intent.getExtras();
+
+ if (extras == null) {
+ extras = new Bundle();
+ }
+
+ /**
+ * com.android.crypto actions
+ */
+ if (ACTION_REGISTER.equals(action)) {
+ final String packageName = extras.getString(EXTRA_PACKAGE_NAME);
+
+ // Inflate a "Done"/"Cancel" custom action bar view
+ final LayoutInflater inflater = (LayoutInflater) getSupportActionBar()
+ .getThemedContext().getSystemService(LAYOUT_INFLATER_SERVICE);
+ final View customActionBarView = inflater.inflate(
+ R.layout.actionbar_custom_view_done_cancel, null);
+
+ ((TextView) customActionBarView.findViewById(R.id.actionbar_done_text))
+ .setText(R.string.api_register_allow);
+ customActionBarView.findViewById(R.id.actionbar_done).setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Allow
+
+ // user needs to select a key!
+ if (settingsFragment.getAppSettings().getKeyId() == Id.key.none) {
+ Toast.makeText(CryptoServiceActivity.this,
+ R.string.api_register_error_select_key, Toast.LENGTH_LONG)
+ .show();
+ } else {
+ ProviderHelper.insertApiApp(CryptoServiceActivity.this,
+ settingsFragment.getAppSettings());
+
+ try {
+ mServiceCallback.onRegistered(true, packageName);
+ } catch (RemoteException e) {
+ Log.e(Constants.TAG, "ServiceActivity");
+ }
+ finish();
+ }
+ }
+ });
+ ((TextView) customActionBarView.findViewById(R.id.actionbar_cancel_text))
+ .setText(R.string.api_register_disallow);
+ customActionBarView.findViewById(R.id.actionbar_cancel).setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Disallow
+ try {
+ mServiceCallback.onRegistered(false, packageName);
+ } catch (RemoteException e) {
+ Log.e(Constants.TAG, "ServiceActivity");
+ }
+ finish();
+ }
+ });
+
+ // Show the custom action bar view and hide the normal Home icon and title.
+ final ActionBar actionBar = getSupportActionBar();
+ actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
+ ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_SHOW_HOME
+ | ActionBar.DISPLAY_SHOW_TITLE);
+ actionBar.setCustomView(customActionBarView, new ActionBar.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+
+ setContentView(R.layout.api_app_register_activity);
+
+ settingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById(
+ R.id.api_app_settings_fragment);
+
+ AppSettings settings = new AppSettings(packageName);
+ settingsFragment.setAppSettings(settings);
+
+
+ // TODO: handle if app is already registered
+ // LinearLayout layoutRegister = (LinearLayout)
+ // findViewById(R.id.register_crypto_consumer_register_layout);
+ // LinearLayout layoutEdit = (LinearLayout)
+ // findViewById(R.id.register_crypto_consumer_edit_layout);
+ //
+ // // if already registered show edit buttons
+ // ArrayList<String> allowedPkgs = ProviderHelper.getCryptoConsumers(this);
+ // if (allowedPkgs.contains(packageName)) {
+ // Log.d(Constants.TAG, "Package is allowed! packageName: " + packageName);
+ // layoutRegister.setVisibility(View.GONE);
+ // layoutEdit.setVisibility(View.VISIBLE);
+ // } else {
+ // layoutRegister.setVisibility(View.VISIBLE);
+ // layoutEdit.setVisibility(View.GONE);
+ // }
+
+ } else if (ACTION_CACHE_PASSPHRASE.equals(action)) {
+ long secretKeyId = extras.getLong(EXTRA_SECRET_KEY_ID);
+
+ showPassphraseDialog(secretKeyId);
+ } else {
+ Log.e(Constants.TAG, "Wrong action!");
+ finish();
+ }
+ }
+
+ /**
+ * Shows passphrase dialog to cache a new passphrase the user enters for using it later for
+ * encryption. Based on mSecretKeyId it asks for a passphrase to open a private key or it asks
+ * for a symmetric passphrase
+ */
+ private void showPassphraseDialog(long secretKeyId) {
+ // Message is received after passphrase is cached
+ Handler returnHandler = new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
+ try {
+ mServiceCallback.onCachedPassphrase(true);
+ } catch (RemoteException e) {
+ Log.e(Constants.TAG, "ServiceActivity");
+ }
+ finish();
+ }
+ }
+ };
+
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(returnHandler);
+
+ try {
+ PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance(this,
+ messenger, secretKeyId);
+
+ passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog");
+ } catch (PgpMain.PgpGeneralException e) {
+ Log.d(Constants.TAG, "No passphrase for this secret key, encrypt directly!");
+ // send message to handler to start encryption directly
+ returnHandler.sendEmptyMessage(PassphraseDialogFragment.MESSAGE_OKAY);
+ }
+ }
+}
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/remote_api/IServiceActivityCallback.aidl b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/remote_api/IServiceActivityCallback.aidl
new file mode 100644
index 000000000..e9949e1ae
--- /dev/null
+++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/remote_api/IServiceActivityCallback.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.sufficientlysecure.keychain.remote_api;
+
+
+interface IServiceActivityCallback {
+
+ oneway void onRegistered(in boolean success, in String packageName);
+
+ oneway void onCachedPassphrase(in boolean success);
+
+} \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/remote_api/RegisteredAppsAdapter.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/remote_api/RegisteredAppsAdapter.java
new file mode 100644
index 000000000..9bf66a90a
--- /dev/null
+++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/remote_api/RegisteredAppsAdapter.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.sufficientlysecure.keychain.remote_api;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.database.Cursor;
+import android.support.v4.widget.CursorAdapter;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+public class RegisteredAppsAdapter extends CursorAdapter {
+
+ private LayoutInflater mInflater;
+ private PackageManager pm;
+
+ public RegisteredAppsAdapter(Context context, Cursor c, int flags) {
+ super(context, c, flags);
+
+ mInflater = LayoutInflater.from(context);
+ pm = context.getApplicationContext().getPackageManager();
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ TextView text = (TextView) view.findViewById(R.id.api_apps_adapter_item_name);
+ ImageView icon = (ImageView) view.findViewById(R.id.api_apps_adapter_item_icon);
+
+ String packageName = cursor.getString(cursor.getColumnIndex(ApiApps.PACKAGE_NAME));
+ if (packageName != null) {
+ // get application name
+ try {
+ ApplicationInfo ai = pm.getApplicationInfo(packageName, 0);
+
+ text.setText(pm.getApplicationLabel(ai));
+ icon.setImageDrawable(pm.getApplicationIcon(ai));
+ } catch (final NameNotFoundException e) {
+ // fallback
+ text.setText(packageName);
+ }
+ } else {
+ // fallback
+ text.setText(packageName);
+ }
+
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ return mInflater.inflate(R.layout.api_apps_adapter_list_item, null);
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/remote_api/RegisteredAppsListActivity.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/remote_api/RegisteredAppsListActivity.java
new file mode 100644
index 000000000..f5487e2a3
--- /dev/null
+++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/remote_api/RegisteredAppsListActivity.java
@@ -0,0 +1,44 @@
+package org.sufficientlysecure.keychain.remote_api;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.ui.MainActivity;
+
+import com.actionbarsherlock.app.ActionBar;
+import com.actionbarsherlock.app.SherlockFragmentActivity;
+import com.actionbarsherlock.view.MenuItem;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+public class RegisteredAppsListActivity extends SherlockFragmentActivity {
+ private ActionBar mActionBar;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mActionBar = getSupportActionBar();
+
+ setContentView(R.layout.api_apps_list_activity);
+
+ mActionBar.setDisplayShowTitleEnabled(true);
+ mActionBar.setDisplayHomeAsUpEnabled(true);
+ }
+
+ /**
+ * Menu Options
+ */
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ // app icon in Action Bar clicked; go home
+ Intent intent = new Intent(this, MainActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+}
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/remote_api/RegisteredAppsListFragment.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/remote_api/RegisteredAppsListFragment.java
new file mode 100644
index 000000000..d1e52a2d6
--- /dev/null
+++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/remote_api/RegisteredAppsListFragment.java
@@ -0,0 +1,90 @@
+package org.sufficientlysecure.keychain.remote_api;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.provider.KeychainContract;
+import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
+
+import android.content.ContentUris;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.CursorLoader;
+import android.support.v4.content.Loader;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+
+import com.actionbarsherlock.app.SherlockListFragment;
+
+public class RegisteredAppsListFragment extends SherlockListFragment implements
+ LoaderManager.LoaderCallbacks<Cursor> {
+
+ // This is the Adapter being used to display the list's data.
+ RegisteredAppsAdapter mAdapter;
+
+ // If non-null, this is the current filter the user has provided.
+ String mCurFilter;
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ getListView().setOnItemClickListener(new OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
+ // edit app settings
+ Intent intent = new Intent(getActivity(), AppSettingsActivity.class);
+ intent.setData(ContentUris.withAppendedId(KeychainContract.ApiApps.CONTENT_URI, id));
+ startActivity(intent);
+ }
+ });
+
+ // Give some text to display if there is no data. In a real
+ // application this would come from a resource.
+ setEmptyText(getString(R.string.api_no_apps));
+
+ // We have a menu item to show in action bar.
+ setHasOptionsMenu(true);
+
+ // Create an empty adapter we will use to display the loaded data.
+ mAdapter = new RegisteredAppsAdapter(getActivity(), null, 0);
+ setListAdapter(mAdapter);
+
+ // Prepare the loader. Either re-connect with an existing one,
+ // or start a new one.
+ getLoaderManager().initLoader(0, null, this);
+ }
+
+ // These are the Contacts rows that we will retrieve.
+ static final String[] CONSUMERS_SUMMARY_PROJECTION = new String[] { ApiApps._ID,
+ ApiApps.PACKAGE_NAME };
+
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ // This is called when a new Loader needs to be created. This
+ // sample only has one Loader, so we don't care about the ID.
+ // First, pick the base URI to use depending on whether we are
+ // currently filtering.
+ Uri baseUri = ApiApps.CONTENT_URI;
+
+ // Now create and return a CursorLoader that will take care of
+ // creating a Cursor for the data being displayed.
+ return new CursorLoader(getActivity(), baseUri, CONSUMERS_SUMMARY_PROJECTION, null, null,
+ ApiApps.PACKAGE_NAME + " COLLATE LOCALIZED ASC");
+ }
+
+ public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+ // Swap the new cursor in. (The framework will take care of closing the
+ // old cursor once we return.)
+ mAdapter.swapCursor(data);
+ }
+
+ public void onLoaderReset(Loader<Cursor> loader) {
+ // This is called when the last Cursor provided to onLoadFinished()
+ // above is about to be closed. We need to make sure we are no
+ // longer using it.
+ mAdapter.swapCursor(null);
+ }
+
+} \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/PassphraseCacheService.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/PassphraseCacheService.java
index d11b8e92a..343e5fbc3 100644
--- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/PassphraseCacheService.java
+++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/PassphraseCacheService.java
@@ -48,6 +48,12 @@ import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;
+/**
+ * This service runs in its own process, but is available to all other processes as the main
+ * passphrase cache. Use the static methods addCachedPassphrase and getCachedPassphrase for
+ * convenience.
+ *
+ */
public class PassphraseCacheService extends Service {
public static final String TAG = Constants.TAG + ": PassphraseCacheService";
@@ -74,9 +80,9 @@ public class PassphraseCacheService extends Service {
Context mContext;
/**
- * This caches a new passphrase by sending a new command to the service. An android service is
- * only run once. Thus, when the service is already started, new commands just add new events to
- * the alarm manager for new passphrases to let them timeout in the future.
+ * This caches a new passphrase in memory by sending a new command to the service. An android
+ * service is only run once. Thus, when the service is already started, new commands just add
+ * new events to the alarm manager for new passphrases to let them timeout in the future.
*
* @param context
* @param keyId
@@ -95,21 +101,23 @@ public class PassphraseCacheService extends Service {
}
/**
- * Gets a cached passphrase from memory, blocking method
+ * Gets a cached passphrase from memory by sending an intent to the service. This method is
+ * designed to wait until the service returns the passphrase.
*
* @param context
* @param keyId
- * @return
+ * @return passphrase or null (if no passphrase is cached for this keyId)
*/
public static String getCachedPassphrase(Context context, long keyId) {
Log.d(TAG, "getCachedPassphrase() get masterKeyId for " + keyId);
+
Intent intent = new Intent(context, PassphraseCacheService.class);
intent.setAction(ACTION_PASSPHRASE_CACHE_GET);
final Object mutex = new Object();
final Bundle returnBundle = new Bundle();
- HandlerThread handlerThread = new HandlerThread("getPassphrase");
+ HandlerThread handlerThread = new HandlerThread("getPassphraseThread");
handlerThread.start();
Handler returnHandler = new Handler(handlerThread.getLooper()) {
@Override
@@ -121,6 +129,7 @@ public class PassphraseCacheService extends Service {
synchronized (mutex) {
mutex.notify();
}
+ // quit handlerThread
getLooper().quit();
}
};
@@ -147,6 +156,12 @@ public class PassphraseCacheService extends Service {
}
}
+ /**
+ * Internal implementation to get cached passphrase.
+ *
+ * @param keyId
+ * @return
+ */
private String getCachedPassphraseImpl(long keyId) {
Log.d(TAG, "getCachedPassphraseImpl() get masterKeyId for " + keyId);
@@ -163,20 +178,20 @@ public class PassphraseCacheService extends Service {
}
masterKeyId = masterKey.getKeyID();
}
- Log.d(TAG, "getCachedPassphraseImpl() for masterKeyId" + masterKeyId);
+ Log.d(TAG, "getCachedPassphraseImpl() for masterKeyId " + masterKeyId);
// get cached passphrase
String cachedPassphrase = mPassphraseCache.get(masterKeyId);
if (cachedPassphrase == null) {
- // TODO: fix!
- // check if secret key has a passphrase
- // if (!hasPassphrase(context, masterKeyId)) {
- // // cache empty passphrase
- // addCachedPassphrase(context, masterKeyId, "");
- // return "";
- // } else {
- return null;
- // }
+ // if key has no passphrase -> cache and return empty passphrase
+ if (!hasPassphrase(this, masterKeyId)) {
+ Log.d(Constants.TAG, "Key has no passphrase! Caches and returns empty passphrase!");
+
+ addCachedPassphrase(this, masterKeyId, "");
+ return "";
+ } else {
+ return null;
+ }
}
// set it again to reset the cache life cycle
Log.d(TAG, "Cache passphrase again when getting it!");
@@ -196,17 +211,10 @@ public class PassphraseCacheService extends Service {
try {
PGPSecretKey secretKey = PgpHelper.getMasterKey(ProviderHelper
.getPGPSecretKeyRingByKeyId(context, secretKeyId));
-
- Log.d(Constants.TAG, "Check if key has no passphrase...");
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
"SC").build("".toCharArray());
PGPPrivateKey testKey = secretKey.extractPrivateKey(keyDecryptor);
if (testKey != null) {
- Log.d(Constants.TAG, "Key has no passphrase! Caches empty passphrase!");
-
- // cache empty passphrase
- PassphraseCacheService.addCachedPassphrase(context, secretKey.getKeyID(), "");
-
return false;
}
} catch (PGPException e) {
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/.SelectSecretKeyFragment.java.kate-swp b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/.SelectSecretKeyFragment.java.kate-swp
new file mode 100644
index 000000000..c72aa8947
--- /dev/null
+++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/.SelectSecretKeyFragment.java.kate-swp
Binary files differ
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/MainActivity.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/MainActivity.java
index 447801e55..3f5ca536f 100644
--- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/MainActivity.java
+++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/MainActivity.java
@@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.ui;
import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.remote_api.RegisteredAppsListActivity;
import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.SherlockActivity;
@@ -80,6 +81,9 @@ public class MainActivity extends SherlockActivity {
menu.add(0, Id.menu.option.preferences, 0, R.string.menu_preferences)
.setIcon(R.drawable.ic_menu_settings)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ menu.add(0, Id.menu.option.crypto_consumers, 0, R.string.menu_apiAppSettings)
+ .setIcon(R.drawable.ic_menu_settings)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_WITH_TEXT);
return true;
}
@@ -91,6 +95,10 @@ public class MainActivity extends SherlockActivity {
startActivity(new Intent(this, PreferencesActivity.class));
return true;
+ case Id.menu.option.crypto_consumers:
+ startActivity(new Intent(this, RegisteredAppsListActivity.class));
+ return true;
+
default:
break;
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java
index 8b3e75d05..b9c42a17c 100644
--- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java
+++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java
@@ -67,7 +67,7 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements
// application this would come from a resource.
setEmptyText(getString(R.string.listEmpty));
- mAdapter = new SelectKeyCursorAdapter(mActivity, mListView, null, Id.type.public_key);
+ mAdapter = new SelectKeyCursorAdapter(mActivity, null, 0, mListView, Id.type.public_key);
setListAdapter(mAdapter);
@@ -160,11 +160,12 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements
+ SelectKeyCursorAdapter.PROJECTION_ROW_AVAILABLE,
"(SELECT COUNT(valid_keys." + Keys._ID + ") FROM " + Tables.KEYS
+ " AS valid_keys WHERE valid_keys." + Keys.KEY_RING_ROW_ID + " = "
- + KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID + " AND valid_keys."
- + Keys.IS_REVOKED + " = '0' AND valid_keys." + Keys.CAN_ENCRYPT
- + " = '1' AND valid_keys." + Keys.CREATION + " <= '" + now + "' AND "
- + "(valid_keys." + Keys.EXPIRY + " IS NULL OR valid_keys." + Keys.EXPIRY
- + " >= '" + now + "')) AS " + SelectKeyCursorAdapter.PROJECTION_ROW_VALID, };
+ + KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID
+ + " AND valid_keys." + Keys.IS_REVOKED + " = '0' AND valid_keys."
+ + Keys.CAN_ENCRYPT + " = '1' AND valid_keys." + Keys.CREATION + " <= '"
+ + now + "' AND " + "(valid_keys." + Keys.EXPIRY + " IS NULL OR valid_keys."
+ + Keys.EXPIRY + " >= '" + now + "')) AS "
+ + SelectKeyCursorAdapter.PROJECTION_ROW_VALID, };
String inMasterKeyList = null;
if (mSelectedMasterKeyIds != null && mSelectedMasterKeyIds.length > 0) {
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/SelectSecretKeyFragment.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/SelectSecretKeyFragment.java
index 4871b74bc..9b87f085c 100644
--- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/SelectSecretKeyFragment.java
+++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/SelectSecretKeyFragment.java
@@ -73,7 +73,7 @@ public class SelectSecretKeyFragment extends SherlockListFragment implements
// application this would come from a resource.
setEmptyText(getString(R.string.listEmpty));
- mAdapter = new SelectKeyCursorAdapter(mActivity, mListView, null, Id.type.secret_key);
+ mAdapter = new SelectKeyCursorAdapter(mActivity, null, 0, mListView, Id.type.secret_key);
setListAdapter(mAdapter);
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/ImportKeysListLoader.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/ImportKeysListLoader.java
index 198140e0c..94d578384 100644
--- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/ImportKeysListLoader.java
+++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/ImportKeysListLoader.java
@@ -23,19 +23,14 @@ import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.spongycastle.openpgp.PGPKeyRing;
import org.spongycastle.openpgp.PGPObjectFactory;
-import org.spongycastle.openpgp.PGPPublicKeyRing;
-import org.spongycastle.openpgp.PGPPublicKeyRingCollection;
import org.spongycastle.openpgp.PGPSecretKeyRing;
-import org.spongycastle.openpgp.PGPSecretKeyRingCollection;
import org.spongycastle.openpgp.PGPUtil;
import org.sufficientlysecure.keychain.Constants;
-import org.sufficientlysecure.keychain.helper.PgpConversionHelper;
import org.sufficientlysecure.keychain.helper.PgpHelper;
import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log;
@@ -45,11 +40,6 @@ import org.sufficientlysecure.keychain.R;
import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;
-/**
- * A custom Loader to search for bad adware apps, based on
- * https://github.com/brosmike/AirPush-Detector. Daniel Bjorge licensed it under Apachev2 after
- * asking him by mail.
- */
public class ImportKeysListLoader extends AsyncTaskLoader<List<Map<String, String>>> {
public static final String MAP_ATTR_USER_ID = "user_id";
public static final String MAP_ATTR_FINGERPINT = "fingerprint";
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/SelectKeyCursorAdapter.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/SelectKeyCursorAdapter.java
index 17423136f..5d8b7d1b1 100644
--- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/SelectKeyCursorAdapter.java
+++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/SelectKeyCursorAdapter.java
@@ -44,9 +44,9 @@ public class SelectKeyCursorAdapter extends CursorAdapter {
public final static String PROJECTION_ROW_AVAILABLE = "available";
public final static String PROJECTION_ROW_VALID = "valid";
- @SuppressWarnings("deprecation")
- public SelectKeyCursorAdapter(Context context, ListView listView, Cursor c, int keyType) {
- super(context, c);
+ public SelectKeyCursorAdapter(Context context, Cursor c, int flags, ListView listView,
+ int keyType) {
+ super(context, c, flags);
mInflater = LayoutInflater.from(context);
mListView = listView;
@@ -65,8 +65,7 @@ public class SelectKeyCursorAdapter extends CursorAdapter {
@Override
public void bindView(View view, Context context, Cursor cursor) {
- boolean valid = cursor.getInt(cursor
- .getColumnIndex(PROJECTION_ROW_VALID)) > 0;
+ boolean valid = cursor.getInt(cursor.getColumnIndex(PROJECTION_ROW_VALID)) > 0;
TextView mainUserId = (TextView) view.findViewById(R.id.mainUserId);
mainUserId.setText(R.string.unknownUserId);
@@ -101,8 +100,7 @@ public class SelectKeyCursorAdapter extends CursorAdapter {
status.setText(R.string.canSign);
}
} else {
- if (cursor.getInt(cursor
- .getColumnIndex(PROJECTION_ROW_AVAILABLE)) > 0) {
+ if (cursor.getInt(cursor.getColumnIndex(PROJECTION_ROW_AVAILABLE)) > 0) {
// has some CAN_ENCRYPT keys, but col(ROW_VALID) = 0, so must be revoked or
// expired
status.setText(R.string.expired);
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/util/PausableThreadPoolExecutor.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/util/PausableThreadPoolExecutor.java
new file mode 100644
index 000000000..d6170a4e2
--- /dev/null
+++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/util/PausableThreadPoolExecutor.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.sufficientlysecure.keychain.util;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.RejectedExecutionHandler;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Example from
+ * http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/ThreadPoolExecutor.html
+ */
+public class PausableThreadPoolExecutor extends ThreadPoolExecutor {
+
+ public PausableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
+ TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
+ super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
+ }
+
+ public PausableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
+ TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
+ RejectedExecutionHandler handler) {
+ super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
+ }
+
+ public PausableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
+ TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
+ super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
+ }
+
+ public PausableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
+ TimeUnit unit, BlockingQueue<Runnable> workQueue) {
+ super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
+ }
+
+ private boolean isPaused;
+ private ReentrantLock pauseLock = new ReentrantLock();
+ private Condition unpaused = pauseLock.newCondition();
+
+ protected void beforeExecute(Thread t, Runnable r) {
+ super.beforeExecute(t, r);
+ pauseLock.lock();
+ try {
+ while (isPaused)
+ unpaused.await();
+ } catch (InterruptedException ie) {
+ t.interrupt();
+ } finally {
+ pauseLock.unlock();
+ }
+ }
+
+ public void pause() {
+ pauseLock.lock();
+ try {
+ isPaused = true;
+ } finally {
+ pauseLock.unlock();
+ }
+ }
+
+ public void resume() {
+ pauseLock.lock();
+ try {
+ isPaused = false;
+ unpaused.signalAll();
+ } finally {
+ pauseLock.unlock();
+ }
+ }
+} \ No newline at end of file