diff options
author | Dominik Schürmann <dominik@dominikschuermann.de> | 2013-09-06 13:52:57 +0200 |
---|---|---|
committer | Dominik Schürmann <dominik@dominikschuermann.de> | 2013-09-06 13:52:57 +0200 |
commit | de8e1a39d59e1a0f40e173ba3c28e4250d1a48ef (patch) | |
tree | 5cbc34684c63888d3e8e775ad35f79d498908cb7 /OpenPGP-Keychain/src | |
parent | b9eaf235621ff59a661b45b20ac1106f42ee4be5 (diff) | |
parent | 3a66c1c25aff1dadf4779c2df0fac04f3a9f3c0a (diff) | |
download | open-keychain-de8e1a39d59e1a0f40e173ba3c28e4250d1a48ef.tar.gz open-keychain-de8e1a39d59e1a0f40e173ba3c28e4250d1a48ef.tar.bz2 open-keychain-de8e1a39d59e1a0f40e173ba3c28e4250d1a48ef.zip |
merge k9mail back into master
Diffstat (limited to 'OpenPGP-Keychain/src')
33 files changed, 1712 insertions, 516 deletions
diff --git a/OpenPGP-Keychain/src/com/android/crypto/CryptoError.aidl b/OpenPGP-Keychain/src/org/openintents/crypto/CryptoError.aidl index d1b941212..7b67c8995 100644 --- a/OpenPGP-Keychain/src/com/android/crypto/CryptoError.aidl +++ b/OpenPGP-Keychain/src/org/openintents/crypto/CryptoError.aidl @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.crypto; +package org.openintents.crypto; // Declare CryptoError so AIDL can find it and knows that it implements the parcelable protocol. parcelable CryptoError;
\ No newline at end of file diff --git a/OpenPGP-Keychain/src/com/android/crypto/CryptoError.java b/OpenPGP-Keychain/src/org/openintents/crypto/CryptoError.java index 9540f4f68..265fe2633 100644 --- a/OpenPGP-Keychain/src/com/android/crypto/CryptoError.java +++ b/OpenPGP-Keychain/src/org/openintents/crypto/CryptoError.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.crypto; +package org.openintents.crypto; import android.os.Parcel; import android.os.Parcelable; diff --git a/OpenPGP-Keychain/src/com/android/crypto/CryptoServiceConnection.java b/OpenPGP-Keychain/src/org/openintents/crypto/CryptoServiceConnection.java index 4d659d344..d9e91f772 100644 --- a/OpenPGP-Keychain/src/com/android/crypto/CryptoServiceConnection.java +++ b/OpenPGP-Keychain/src/org/openintents/crypto/CryptoServiceConnection.java @@ -1,4 +1,22 @@ -package com.android.crypto; +/* + * 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.openintents.crypto; + +import org.openintents.crypto.ICryptoService; import android.content.ComponentName; import android.content.Context; @@ -50,8 +68,8 @@ public class CryptoServiceConnection { Log.d(TAG, "not bound yet"); Intent serviceIntent = new Intent(); - serviceIntent.setAction("com.android.crypto.ICryptoService"); - serviceIntent.setPackage(cryptoProviderPackageName); // TODO: test + serviceIntent.setAction("org.openintents.crypto.ICryptoService"); + serviceIntent.setPackage(cryptoProviderPackageName); mApplicationContext.bindService(serviceIntent, mCryptoServiceConnection, Context.BIND_AUTO_CREATE); diff --git a/OpenPGP-Keychain/src/com/android/crypto/CryptoSignatureResult.aidl b/OpenPGP-Keychain/src/org/openintents/crypto/CryptoSignatureResult.aidl index 21862c497..1d39bac70 100644 --- a/OpenPGP-Keychain/src/com/android/crypto/CryptoSignatureResult.aidl +++ b/OpenPGP-Keychain/src/org/openintents/crypto/CryptoSignatureResult.aidl @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.crypto; +package org.openintents.crypto; // Declare CryptoSignatureResult so AIDL can find it and knows that it implements the parcelable protocol. parcelable CryptoSignatureResult;
\ No newline at end of file diff --git a/OpenPGP-Keychain/src/com/android/crypto/CryptoSignatureResult.java b/OpenPGP-Keychain/src/org/openintents/crypto/CryptoSignatureResult.java index 87f5f43b5..e193b73b3 100644 --- a/OpenPGP-Keychain/src/com/android/crypto/CryptoSignatureResult.java +++ b/OpenPGP-Keychain/src/org/openintents/crypto/CryptoSignatureResult.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.crypto; +package org.openintents.crypto; import android.os.Parcel; import android.os.Parcelable; diff --git a/OpenPGP-Keychain/src/com/android/crypto/ICryptoCallback.aidl b/OpenPGP-Keychain/src/org/openintents/crypto/ICryptoCallback.aidl index af6587c04..1f910d4c6 100644 --- a/OpenPGP-Keychain/src/com/android/crypto/ICryptoCallback.aidl +++ b/OpenPGP-Keychain/src/org/openintents/crypto/ICryptoCallback.aidl @@ -14,19 +14,19 @@ * limitations under the License. */ -package com.android.crypto; +package org.openintents.crypto; -import com.android.crypto.CryptoSignatureResult; -import com.android.crypto.CryptoError; +import org.openintents.crypto.CryptoSignatureResult; +import org.openintents.crypto.CryptoError; interface ICryptoCallback { - - oneway void onEncryptSignSuccess(in byte[] outputBytes); - oneway void onDecryptVerifySuccess(in byte[] outputBytes, in CryptoSignatureResult signatureResult); + /** + * CryptoSignatureResult is only returned if the Callback was used from decryptAndVerify + * + */ + oneway void onSuccess(in byte[] outputBytes, in CryptoSignatureResult signatureResult); oneway void onError(in CryptoError error); - - oneway void onActivityRequired(in Intent intent); }
\ No newline at end of file diff --git a/OpenPGP-Keychain/src/com/android/crypto/ICryptoService.aidl b/OpenPGP-Keychain/src/org/openintents/crypto/ICryptoService.aidl index 05baa16e0..c84ca28fb 100644 --- a/OpenPGP-Keychain/src/com/android/crypto/ICryptoService.aidl +++ b/OpenPGP-Keychain/src/org/openintents/crypto/ICryptoService.aidl @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.android.crypto; +package org.openintents.crypto; -import com.android.crypto.ICryptoCallback; +import org.openintents.crypto.ICryptoCallback; /** * All methods are oneway, which means they are asynchronous and non-blocking. @@ -29,40 +29,38 @@ interface ICryptoService { * * @param inputBytes * Byte array you want to encrypt - * @param encryptionKeyIds - * Ids of public keys used for encryption - * @param handler - * Results are returned to this Handler after successful encryption + * @param encryptionUserIds + * User Ids (emails) of recipients + * @param callback + * Callback where to return results */ oneway void encrypt(in byte[] inputBytes, in String[] encryptionUserIds, in ICryptoCallback callback); /** - * Encrypt and sign + * Sign * - * - * * @param inputBytes * Byte array you want to encrypt - * @param signatureKeyId - * Key id of key to sign with - * @param handler - * Results are returned to this Handler after successful encryption and signing + * @param signatureUserId + * User Ids (email) of sender + * @param callback + * Callback where to return results */ - oneway void encryptAndSign(in byte[] inputBytes, in String[] encryptionUserIds, String signatureUserId, in ICryptoCallback callback); + oneway void sign(in byte[] inputBytes, String signatureUserId, in ICryptoCallback callback); /** - * Sign + * Encrypt and sign * - * - * * @param inputBytes * Byte array you want to encrypt - * @param signatureId - * - * @param handler - * Results are returned to this Handler after successful encryption and signing + * @param encryptionUserIds + * User Ids (emails) of recipients + * @param signatureUserId + * User Ids (email) of sender + * @param callback + * Callback where to return results */ - oneway void sign(in byte[] inputBytes, String signatureUserId, in ICryptoCallback callback); + oneway void encryptAndSign(in byte[] inputBytes, in String[] encryptionUserIds, String signatureUserId, in ICryptoCallback callback); /** * Decrypts and verifies given input bytes. If no signature is present this method @@ -70,9 +68,15 @@ interface ICryptoService { * * @param inputBytes * Byte array you want to decrypt and verify - * @param handler - * Handler where to return results to after successful encryption + * @param callback + * Callback where to return results + */ + oneway void decryptAndVerify(in byte[] inputBytes, in ICryptoCallback callback); + + /** + * Opens setup using default parameters + * */ - oneway void decryptAndVerify(in byte[] inputBytes, in ICryptoCallback callback); + oneway void setup(boolean asciiArmor, boolean newKeyring, String newKeyringUserId); }
\ No newline at end of file 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 Binary files differnew file mode 100644 index 000000000..c72aa8947 --- /dev/null +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/.SelectSecretKeyFragment.java.kate-swp 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 |