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 | |
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
65 files changed, 2838 insertions, 852 deletions
@@ -0,0 +1,34 @@ +# Security Model + +## Basic goals + +* Intents without permissions should only work based on user interaction (e.g. click a button in a dialog) + +Android primitives to exchange data: Intent, Intent with return values, Send (also an Intent), Content Provider, AIDL + +## Without Permissions + +### Intents +All Intents start with ``org.sufficientlysecure.keychain.action.`` + +* ``android.intent.action.VIEW`` connected to .gpg and .asc files: Import Key and Decrypt +* ``android.intent.action.SEND connected to all mime types (text/plain and every binary data like files and images): Encrypt and Decrypt +* ``IMPORT`` +* ``IMPORT_FROM_FILE`` +* ``IMPORT_FROM_QR_CODE`` +* ``IMPORT_FROM_NFC`` +* ``SHARE_KEYRING`` +* ``SHARE_KEYRING_WITH_QR_CODE`` +* ``SHARE_KEYRING_WITH_NFC`` +* ``EDIT_KEYRING`` +* ``SELECT_PUBLIC_KEYRINGS`` +* ``SELECT_SECRET_KEYRING`` +* ``ENCRYPT`` +* ``ENCRYPT_FILE`` +* ``DECRYPT`` +* ``DECRYPT_FILE`` + +TODO: +- remove IMPORT, SHARE intents, simplify ENCRYPT and DECRYPT intents (include _FILE derivates like done in SEND based on file type) +- EDIT_KEYRING and CREATE_KEYRING, should be available via for registered apps +- new intent REGISTER_APP?
\ No newline at end of file diff --git a/OLD_API.md b/OLD_API.md new file mode 100644 index 000000000..1a9bab0a7 --- /dev/null +++ b/OLD_API.md @@ -0,0 +1,68 @@ +This is the old API. Currently disabled! + +# Security Model + +## Basic goals + +* Intents without permissions should only work based on user interaction (e.g. click a button in a dialog) + +Android primitives to exchange data: Intent, Intent with return values, Send (also an Intent), Content Provider, AIDL + +## Possible Permissions + +* ACCESS_API: Encrypt/Sign/Decrypt/Create keys without user interaction (intents, remote service), Read key information (not the actual keys)(content provider) +* ACCESS_KEYS: get and import actual public and secret keys (remote service) + + +## Without Permissions + +### Intents +All Intents start with org.sufficientlysecure.keychain.action. + +* android.intent.action.VIEW connected to .gpg and .asc files: Import Key and Decrypt +* android.intent.action.SEND connected to all mime types (text/plain and every binary data like files and images): Encrypt and Decrypt +* IMPORT +* IMPORT_FROM_FILE +* IMPORT_FROM_QR_CODE +* IMPORT_FROM_NFC +* SHARE_KEYRING +* SHARE_KEYRING_WITH_QR_CODE +* SHARE_KEYRING_WITH_NFC +* EDIT_KEYRING +* SELECT_PUBLIC_KEYRINGS +* SELECT_SECRET_KEYRING +* ENCRYPT +* ENCRYPT_FILE +* DECRYPT +* DECRYPT_FILE + +## With permission ACCESS_API + +### Intents + +* CREATE_KEYRING +* ENCRYPT_AND_RETURN +* ENCRYPT_STREAM_AND_RETURN +* GENERATE_SIGNATURE_AND_RETURN +* DECRYPT_AND_RETURN +* DECRYPT_STREAM_AND_RETURN + +### Broadcast Receiver +On change of database the following broadcast is send. +* DATABASE_CHANGE + +### Content Provider + +* The whole content provider requires a permission (only read) +* Don't give out blobs (keys can be accessed by ACCESS_KEYS via remote service) +* Make an internal and external content provider (or pathes with <path-permission>) +* Look at android:grantUriPermissions especially for ApgServiceBlobProvider +* Only give out android:readPermission + +### ApgApiService (Remote Service) +AIDL service + +## With permission ACCESS_KEYS + +### ApgKeyService (Remote Service) +AIDL service to access actual private keyring objects
\ No newline at end of file diff --git a/OpenPGP-Keychain-API-Demo/res/layout/crypto_provider_demo.xml b/OpenPGP-Keychain-API-Demo/res/layout/crypto_provider_demo.xml index 447734a52..af1e345d6 100644 --- a/OpenPGP-Keychain-API-Demo/res/layout/crypto_provider_demo.xml +++ b/OpenPGP-Keychain-API-Demo/res/layout/crypto_provider_demo.xml @@ -8,65 +8,72 @@ android:layout_height="match_parent" android:orientation="vertical" > - <Button - android:id="@+id/crypto_provider_demo_register" - android:layout_width="match_parent" + <TextView + android:layout_width="wrap_content" android:layout_height="wrap_content" - android:onClick="registerCryptoProvider" - android:text="Register crypto provider" /> + android:text="Encrypt User Id" + android:textAppearance="?android:attr/textAppearanceMedium" /> - <Button - android:id="@+id/aidl_demo_select_secret_key" + <EditText + android:id="@+id/crypto_provider_demo_encrypt_user_id" android:layout_width="match_parent" android:layout_height="wrap_content" - android:onClick="selectSecretKeyOnClick" - android:text="Select secret key" /> + android:text="dominik@dominikschuermann.de" + android:textAppearance="@android:style/TextAppearance.Small" /> - <Button - android:id="@+id/aidl_demo_select_encryption_key" - android:layout_width="match_parent" + <TextView + android:layout_width="wrap_content" android:layout_height="wrap_content" - android:onClick="selectEncryptionKeysOnClick" - android:text="Select encryption key(s)" /> + android:text="Message" + android:textAppearance="?android:attr/textAppearanceMedium" /> <EditText - android:id="@+id/aidl_demo_message" + android:id="@+id/crypto_provider_demo_message" android:layout_width="match_parent" android:layout_height="150dip" android:text="message" android:textAppearance="@android:style/TextAppearance.Small" /> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Ciphertext" + android:textAppearance="?android:attr/textAppearanceMedium" /> + <EditText - android:id="@+id/aidl_demo_ciphertext" + android:id="@+id/crypto_provider_demo_ciphertext" android:layout_width="match_parent" android:layout_height="150dip" android:text="ciphertext" android:textAppearance="@android:style/TextAppearance.Small" /> <Button - android:id="@+id/aidl_demo_encrypt" + android:id="@+id/crypto_provider_demo_encrypt" android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="encryptOnClick" android:text="Encrypt" /> <Button - android:id="@+id/aidl_demo_decrypt" + android:id="@+id/crypto_provider_demo_sign" android:layout_width="match_parent" android:layout_height="wrap_content" - android:onClick="decryptOnClick" - android:text="Decrypt" /> + android:onClick="signOnClick" + android:text="Sign" /> - <TextView + <Button + android:id="@+id/crypto_provider_demo_encrypt_and_sign" android:layout_width="match_parent" - android:layout_height="match_parent" - android:text="APG Data:" /> + android:layout_height="wrap_content" + android:onClick="encryptAndSignOnClick" + android:text="Encrypt and Sign" /> - <TextView - android:id="@+id/aidl_demo_data" + <Button + android:id="@+id/crypto_provider_demo_decrypt" android:layout_width="match_parent" - android:layout_height="match_parent" - android:minLines="10" /> + android:layout_height="wrap_content" + android:onClick="decryptOnClick" + android:text="Decrypt" /> </LinearLayout> </ScrollView>
\ No newline at end of file diff --git a/OpenPGP-Keychain/src/com/android/crypto/CryptoError.aidl b/OpenPGP-Keychain-API-Demo/src/org/openintents/crypto/CryptoError.aidl index d1b941212..7b67c8995 100644 --- a/OpenPGP-Keychain/src/com/android/crypto/CryptoError.aidl +++ b/OpenPGP-Keychain-API-Demo/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-API-Demo/src/org/openintents/crypto/CryptoError.java index 9540f4f68..265fe2633 100644 --- a/OpenPGP-Keychain/src/com/android/crypto/CryptoError.java +++ b/OpenPGP-Keychain-API-Demo/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-API-Demo/src/org/openintents/crypto/CryptoServiceConnection.java index 4d659d344..d9e91f772 100644 --- a/OpenPGP-Keychain/src/com/android/crypto/CryptoServiceConnection.java +++ b/OpenPGP-Keychain-API-Demo/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-API-Demo/src/org/openintents/crypto/CryptoSignatureResult.aidl index 21862c497..1d39bac70 100644 --- a/OpenPGP-Keychain/src/com/android/crypto/CryptoSignatureResult.aidl +++ b/OpenPGP-Keychain-API-Demo/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-API-Demo/src/org/openintents/crypto/CryptoSignatureResult.java index 87f5f43b5..e193b73b3 100644 --- a/OpenPGP-Keychain/src/com/android/crypto/CryptoSignatureResult.java +++ b/OpenPGP-Keychain-API-Demo/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-API-Demo/src/org/openintents/crypto/ICryptoCallback.aidl index af6587c04..1f910d4c6 100644 --- a/OpenPGP-Keychain/src/com/android/crypto/ICryptoCallback.aidl +++ b/OpenPGP-Keychain-API-Demo/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-API-Demo/src/org/openintents/crypto/ICryptoService.aidl index 05baa16e0..c84ca28fb 100644 --- a/OpenPGP-Keychain/src/com/android/crypto/ICryptoService.aidl +++ b/OpenPGP-Keychain-API-Demo/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-API-Demo/src/org/sufficientlysecure/keychain/demo/CryptoProviderDemoActivity.java b/OpenPGP-Keychain-API-Demo/src/org/sufficientlysecure/keychain/demo/CryptoProviderDemoActivity.java index b915e2a76..319820d7c 100644 --- a/OpenPGP-Keychain-API-Demo/src/org/sufficientlysecure/keychain/demo/CryptoProviderDemoActivity.java +++ b/OpenPGP-Keychain-API-Demo/src/org/sufficientlysecure/keychain/demo/CryptoProviderDemoActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de> + * 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. @@ -16,51 +16,42 @@ package org.sufficientlysecure.keychain.demo; +import java.util.ArrayList; +import java.util.List; + +import org.openintents.crypto.CryptoError; +import org.openintents.crypto.CryptoServiceConnection; +import org.openintents.crypto.CryptoSignatureResult; +import org.openintents.crypto.ICryptoCallback; +import org.openintents.crypto.ICryptoService; import org.sufficientlysecure.keychain.demo.R; import org.sufficientlysecure.keychain.integration.Constants; -import org.sufficientlysecure.keychain.integration.KeychainData; -import org.sufficientlysecure.keychain.integration.KeychainIntentHelper; -import org.sufficientlysecure.keychain.service.IKeychainApiService; -import org.sufficientlysecure.keychain.service.IKeychainKeyService; -import org.sufficientlysecure.keychain.service.handler.IKeychainDecryptHandler; -import org.sufficientlysecure.keychain.service.handler.IKeychainEncryptHandler; -import org.sufficientlysecure.keychain.service.handler.IKeychainGetDecryptionKeyIdHandler; import android.app.Activity; import android.app.AlertDialog; -import android.content.ActivityNotFoundException; -import android.content.ComponentName; -import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; -import android.content.ServiceConnection; +import android.content.pm.ResolveInfo; +import android.graphics.drawable.Drawable; import android.os.Bundle; -import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.ListAdapter; import android.widget.TextView; -import android.widget.Toast; public class CryptoProviderDemoActivity extends Activity { Activity mActivity; - TextView mMessageTextView; - TextView mCiphertextTextView; - TextView mDataTextView; - - KeychainIntentHelper mKeychainIntentHelper; - KeychainData mKeychainData; + EditText mMessage; + EditText mCiphertext; + EditText mEncryptUserId; + EditText mSignUserId; - private IKeychainApiService service = null; - private ServiceConnection svcConn = new ServiceConnection() { - public void onServiceConnected(ComponentName className, IBinder binder) { - service = IKeychainApiService.Stub.asInterface(binder); - } - - public void onServiceDisconnected(ComponentName className) { - service = null; - } - }; + private CryptoServiceConnection mCryptoServiceConnection; @Override public void onCreate(Bundle icicle) { @@ -69,170 +60,208 @@ public class CryptoProviderDemoActivity extends Activity { mActivity = this; - mMessageTextView = (TextView) findViewById(R.id.aidl_demo_message); - mCiphertextTextView = (TextView) findViewById(R.id.aidl_demo_ciphertext); - mDataTextView = (TextView) findViewById(R.id.aidl_demo_data); - - mKeychainIntentHelper = new KeychainIntentHelper(mActivity); - mKeychainData = new KeychainData(); + mMessage = (EditText) findViewById(R.id.crypto_provider_demo_message); + mCiphertext = (EditText) findViewById(R.id.crypto_provider_demo_ciphertext); + mEncryptUserId = (EditText) findViewById(R.id.crypto_provider_demo_encrypt_user_id); - bindService(new Intent(IKeychainApiService.class.getName()), svcConn, - Context.BIND_AUTO_CREATE); + selectCryptoProvider(); } - public void registerCryptoProvider(View view) { - try { - startActivityForResult(Intent.createChooser(new Intent("com.android.crypto.REGISTER"), - "select crypto provider"), 123); - } catch (ActivityNotFoundException e) { - Toast.makeText(mActivity, "No app that handles com.android.crypto.REGISTER!", - Toast.LENGTH_LONG).show(); - Log.e(Constants.TAG, "No app that handles com.android.crypto.REGISTER!"); + /** + * Callback from remote crypto service + */ + final ICryptoCallback.Stub encryptCallback = new ICryptoCallback.Stub() { + + @Override + public void onSuccess(final byte[] outputBytes, CryptoSignatureResult signatureResult) + throws RemoteException { + Log.d(Constants.TAG, "onEncryptSignSuccess"); + + runOnUiThread(new Runnable() { + + @Override + public void run() { + mCiphertext.setText(new String(outputBytes)); + + } + }); } - } - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == 123) { - if (resultCode == RESULT_OK) { - String packageName = data.getStringExtra("packageName"); - Log.d(Constants.TAG, "packageName: " + packageName); - } + @Override + public void onError(CryptoError error) throws RemoteException { + Log.e(Constants.TAG, "onError getErrorId:" + error.getErrorId()); + Log.e(Constants.TAG, "onError getMessage:" + error.getMessage()); } - // boolean result = mKeychainIntentHelper.onActivityResult(requestCode, resultCode, data, - // mKeychainData); - // if (result) { - // updateView(); - // } + }; - // continue with other activity results - super.onActivityResult(requestCode, resultCode, data); - } + final ICryptoCallback.Stub decryptCallback = new ICryptoCallback.Stub() { + + @Override + public void onSuccess(final byte[] outputBytes, final CryptoSignatureResult signatureResult) + throws RemoteException { + Log.d(Constants.TAG, "onDecryptVerifySuccess"); + + runOnUiThread(new Runnable() { + + @Override + public void run() { + mMessage.setText(new String(outputBytes) + "\n\n" + signatureResult.toString()); + + } + }); + + } + + @Override + public void onError(CryptoError error) throws RemoteException { + Log.e(Constants.TAG, "onError getErrorId:" + error.getErrorId()); + Log.e(Constants.TAG, "onError getMessage:" + error.getMessage()); + } + + }; public void encryptOnClick(View view) { - byte[] inputBytes = mMessageTextView.getText().toString().getBytes(); + byte[] inputBytes = mMessage.getText().toString().getBytes(); try { - service.encryptAsymmetric(inputBytes, null, true, 0, mKeychainData.getPublicKeys(), 7, - encryptHandler); + mCryptoServiceConnection.getService().encrypt(inputBytes, + new String[] { mEncryptUserId.getText().toString() }, encryptCallback); } catch (RemoteException e) { - exceptionImplementation(-1, e.toString()); + Log.e(Constants.TAG, "CryptoProviderDemo", e); } } - public void decryptOnClick(View view) { - byte[] inputBytes = mCiphertextTextView.getText().toString().getBytes(); + public void signOnClick(View view) { + byte[] inputBytes = mMessage.getText().toString().getBytes(); try { - service.decryptAndVerifyAsymmetric(inputBytes, null, null, decryptHandler); + mCryptoServiceConnection.getService().sign(inputBytes, + mSignUserId.getText().toString(), encryptCallback); } catch (RemoteException e) { - exceptionImplementation(-1, e.toString()); + Log.e(Constants.TAG, "CryptoProviderDemo", e); } } - private void updateView() { - if (mKeychainData.getDecryptedData() != null) { - mMessageTextView.setText(mKeychainData.getDecryptedData()); + public void encryptAndSignOnClick(View view) { + byte[] inputBytes = mMessage.getText().toString().getBytes(); + + try { + mCryptoServiceConnection.getService().encryptAndSign(inputBytes, + new String[] { mEncryptUserId.getText().toString() }, + mSignUserId.getText().toString(), encryptCallback); + } catch (RemoteException e) { + Log.e(Constants.TAG, "CryptoProviderDemo", e); } - if (mKeychainData.getEncryptedData() != null) { - mCiphertextTextView.setText(mKeychainData.getEncryptedData()); + } + + public void decryptOnClick(View view) { + byte[] inputBytes = mCiphertext.getText().toString().getBytes(); + + try { + mCryptoServiceConnection.getService().decryptAndVerify(inputBytes, decryptCallback); + } catch (RemoteException e) { + Log.e(Constants.TAG, "CryptoProviderDemo", e); } - mDataTextView.setText(mKeychainData.toString()); } @Override public void onDestroy() { super.onDestroy(); - unbindService(svcConn); - } - - private void exceptionImplementation(int exceptionId, String error) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle("Exception!").setMessage(error).setPositiveButton("OK", null).show(); + if (mCryptoServiceConnection != null) { + mCryptoServiceConnection.unbindFromService(); + } } - private final IKeychainEncryptHandler.Stub encryptHandler = new IKeychainEncryptHandler.Stub() { + private static class CryptoProviderElement { + private String packageName; + private String simpleName; + private Drawable icon; - @Override - public void onException(final int exceptionId, final String message) throws RemoteException { - runOnUiThread(new Runnable() { - public void run() { - exceptionImplementation(exceptionId, message); - } - }); + public CryptoProviderElement(String packageName, String simpleName, Drawable icon) { + this.packageName = packageName; + this.simpleName = simpleName; + this.icon = icon; } @Override - public void onSuccess(final byte[] outputBytes, String outputUri) throws RemoteException { - runOnUiThread(new Runnable() { - public void run() { - mKeychainData.setEncryptedData(new String(outputBytes)); - updateView(); - } - }); + public String toString() { + return simpleName; } + } - }; + private void selectCryptoProvider() { + Intent intent = new Intent(ICryptoService.class.getName()); - private final IKeychainDecryptHandler.Stub decryptHandler = new IKeychainDecryptHandler.Stub() { + final ArrayList<CryptoProviderElement> providerList = new ArrayList<CryptoProviderElement>(); - @Override - public void onException(final int exceptionId, final String message) throws RemoteException { - runOnUiThread(new Runnable() { - public void run() { - exceptionImplementation(exceptionId, message); - } - }); - } + List<ResolveInfo> resInfo = getPackageManager().queryIntentServices(intent, 0); + if (!resInfo.isEmpty()) { + for (ResolveInfo resolveInfo : resInfo) { + if (resolveInfo.serviceInfo == null) + continue; - @Override - public void onSuccess(final byte[] outputBytes, String outputUri, boolean signature, - long signatureKeyId, String signatureUserId, boolean signatureSuccess, - boolean signatureUnknown) throws RemoteException { - runOnUiThread(new Runnable() { - public void run() { - mKeychainData.setDecryptedData(new String(outputBytes)); - updateView(); - } - }); + String packageName = resolveInfo.serviceInfo.packageName; + String simpleName = String.valueOf(resolveInfo.serviceInfo + .loadLabel(getPackageManager())); + Drawable icon = resolveInfo.serviceInfo.loadIcon(getPackageManager()); + providerList.add(new CryptoProviderElement(packageName, simpleName, icon)); + } - } + AlertDialog.Builder alert = new AlertDialog.Builder(this); + alert.setTitle("Select Crypto Provider!"); + alert.setCancelable(false); - }; + if (!providerList.isEmpty()) { - private final IKeychainGetDecryptionKeyIdHandler.Stub helperHandler = new IKeychainGetDecryptionKeyIdHandler.Stub() { + // Init ArrayAdapter with Crypto Providers + ListAdapter adapter = new ArrayAdapter<CryptoProviderElement>(this, + android.R.layout.select_dialog_item, android.R.id.text1, providerList) { + public View getView(int position, View convertView, ViewGroup parent) { + // User super class to create the View + View v = super.getView(position, convertView, parent); + TextView tv = (TextView) v.findViewById(android.R.id.text1); - @Override - public void onException(final int exceptionId, final String message) throws RemoteException { - runOnUiThread(new Runnable() { - public void run() { - exceptionImplementation(exceptionId, message); - } - }); - } + // Put the image on the TextView + tv.setCompoundDrawablesWithIntrinsicBounds(providerList.get(position).icon, + null, null, null); - @Override - public void onSuccess(long arg0, boolean arg1) throws RemoteException { - // TODO Auto-generated method stub + // Add margin between image and text (support various screen densities) + int dp5 = (int) (5 * getResources().getDisplayMetrics().density + 0.5f); + tv.setCompoundDrawablePadding(dp5); - } + return v; + } + }; - }; + alert.setSingleChoiceItems(adapter, -1, new DialogInterface.OnClickListener() { - /** - * Selection is done with Intents, not AIDL! - * - * @param view - */ - public void selectSecretKeyOnClick(View view) { - mKeychainIntentHelper.selectSecretKey(); - } + public void onClick(DialogInterface dialog, int position) { + String packageName = providerList.get(position).packageName; - public void selectEncryptionKeysOnClick(View view) { - mKeychainIntentHelper.selectPublicKeys("user@example.com"); + // bind to service + mCryptoServiceConnection = new CryptoServiceConnection( + CryptoProviderDemoActivity.this, packageName); + mCryptoServiceConnection.bindToService(); - } + dialog.dismiss(); + } + }); + } else { + alert.setMessage("No Crypto Provider installed!"); + } + alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + finish(); + } + }); + + AlertDialog ad = alert.create(); + ad.show(); + } + } } diff --git a/OpenPGP-Keychain/AndroidManifest.xml b/OpenPGP-Keychain/AndroidManifest.xml index d75350048..6de9221ca 100644 --- a/OpenPGP-Keychain/AndroidManifest.xml +++ b/OpenPGP-Keychain/AndroidManifest.xml @@ -67,24 +67,27 @@ <uses-permission android:name="android.permission.NFC" /> <uses-permission android:name="com.fsck.k9.permission.READ_ATTACHMENT" /> - <permission-group - android:name="org.sufficientlysecure.keychain.permission-group.keychain" - android:description="@string/permission_group_description" - android:icon="@drawable/icon" - android:label="@string/permission_group_label" /> - - <permission - android:name="org.sufficientlysecure.keychain.permission.ACCESS_KEYS" - android:description="@string/permission_access_keys_description" - android:label="@string/permission_access_keys_label" - android:permissionGroup="org.sufficientlysecure.keychain.permission-group.keychain" - android:protectionLevel="dangerous" /> - <permission - android:name="org.sufficientlysecure.keychain.permission.ACCESS_API" - android:description="@string/permission_access_api_description" - android:label="@string/permission_access_api_label" - android:permissionGroup="org.sufficientlysecure.keychain.permission-group.keychain" - android:protectionLevel="dangerous" /> + <!-- TODO: disabled, old API --> + <!-- <permission-group --> + <!-- android:name="org.sufficientlysecure.keychain.permission-group.keychain" --> + <!-- android:description="@string/permission_group_description" --> + <!-- android:icon="@drawable/icon" --> + <!-- android:label="@string/permission_group_label" /> --> + + + <!-- <permission --> + <!-- android:name="org.sufficientlysecure.keychain.permission.ACCESS_KEYS" --> + <!-- android:description="@string/permission_access_keys_description" --> + <!-- android:label="@string/permission_access_keys_label" --> + <!-- android:permissionGroup="org.sufficientlysecure.keychain.permission-group.keychain" --> + <!-- android:protectionLevel="dangerous" /> --> + <!-- <permission --> + <!-- android:name="org.sufficientlysecure.keychain.permission.ACCESS_API" --> + <!-- android:description="@string/permission_access_api_description" --> + <!-- android:label="@string/permission_access_api_label" --> + <!-- android:permissionGroup="org.sufficientlysecure.keychain.permission-group.keychain" --> + <!-- android:protectionLevel="dangerous" /> --> + <!-- android:allowBackup="false": Don't allow backup over adb backup or other apps! --> <application @@ -253,6 +256,7 @@ <data android:scheme="file" /> <data android:scheme="content" /> <data android:mimeType="*/*" /> + <!-- Workaround to match files in pathes with dots in them, like /cdcard/my.folder/test.gpg --> <data android:pathPattern=".*\\.gpg" /> <data android:pathPattern=".*\\..*\\.gpg" /> <data android:pathPattern=".*\\..*\\..*\\.gpg" /> @@ -411,82 +415,92 @@ android:exported="false" android:process=":passphrase_cache" /> <service android:name="org.sufficientlysecure.keychain.service.KeychainIntentService" /> - <service - android:name="org.sufficientlysecure.keychain.service.KeychainApiService" - android:enabled="true" - android:exported="true" - android:permission="org.sufficientlysecure.keychain.permission.ACCESS_API" - android:process=":remoteapi" > - <intent-filter> - <action android:name="org.sufficientlysecure.keychain.service.IKeychainApiService" /> - </intent-filter> - - <meta-data - android:name="api_version" - android:value="3" /> - </service> - <service - android:name="org.sufficientlysecure.keychain.service.KeychainKeyService" - android:enabled="true" - android:exported="true" - android:permission="org.sufficientlysecure.keychain.permission.ACCESS_KEYS" - android:process=":remotekeys" > - <intent-filter> - <action android:name="org.sufficientlysecure.keychain.service.IKeychainKeyService" /> - </intent-filter> - <meta-data - android:name="api_version" - android:value="3" /> - </service> + <!-- TODO: disabled, old API! --> + <!-- <service --> + <!-- android:name="org.sufficientlysecure.keychain.service.KeychainApiService" --> + <!-- android:enabled="true" --> + <!-- android:exported="true" --> + <!-- android:permission="org.sufficientlysecure.keychain.permission.ACCESS_API" --> + <!-- android:process=":remoteapi" > --> + <!-- <intent-filter> --> + <!-- <action android:name="org.sufficientlysecure.keychain.service.IKeychainApiService" /> --> + <!-- </intent-filter> --> + + + <!-- <meta-data --> + <!-- android:name="api_version" --> + <!-- android:value="3" /> --> + <!-- </service> --> + <!-- <service --> + <!-- android:name="org.sufficientlysecure.keychain.service.KeychainKeyService" --> + <!-- android:enabled="true" --> + <!-- android:exported="true" --> + <!-- android:permission="org.sufficientlysecure.keychain.permission.ACCESS_KEYS" --> + <!-- android:process=":remotekeys" > --> + <!-- <intent-filter> --> + <!-- <action android:name="org.sufficientlysecure.keychain.service.IKeychainKeyService" /> --> + <!-- </intent-filter> --> + + + <!-- <meta-data --> + <!-- android:name="api_version" --> + <!-- android:value="3" /> --> + <!-- </service> --> <provider android:name="org.sufficientlysecure.keychain.provider.KeychainProviderInternal" android:authorities="org.sufficientlysecure.keychain.internal" android:exported="false" /> - <provider - android:name="org.sufficientlysecure.keychain.provider.KeychainProviderExternal" - android:authorities="org.sufficientlysecure.keychain" - android:exported="true" - android:readPermission="org.sufficientlysecure.keychain.permission.ACCESS_API" /> + <!-- TODO: disabled, old API --> + <!-- <provider --> + <!-- android:name="org.sufficientlysecure.keychain.provider.KeychainProviderExternal" --> + <!-- android:authorities="org.sufficientlysecure.keychain" --> + <!-- android:exported="true" --> + <!-- android:readPermission="org.sufficientlysecure.keychain.permission.ACCESS_API" /> --> + <!-- TODO: authority! --> - <provider - android:name="org.sufficientlysecure.keychain.provider.KeychainServiceBlobProvider" - android:authorities="org.sufficientlysecure.keychain.provider.apgserviceblobprovider" - android:permission="org.sufficientlysecure.keychain.permission.ACCESS_API" /> + <!-- <provider --> + <!-- android:name="org.sufficientlysecure.keychain.provider.KeychainServiceBlobProvider" --> + <!-- android:authorities="org.sufficientlysecure.keychain.provider.apgserviceblobprovider" --> + <!-- android:permission="org.sufficientlysecure.keychain.permission.ACCESS_API" /> --> - <!-- Crypto Provider other intents --> - <activity - android:name=".crypto_provider.CryptoActivity" - android:label="TODO crypto activity" - android:process=":crypto" > - <intent-filter> - <action android:name="org.sufficientlysecure.keychain.CRYPTO_CACHE_PASSPHRASE" /> - <category android:name="android.intent.category.DEFAULT" /> - </intent-filter> - </activity> + <!-- Remote API internal intents --> - <!-- Crypto Provider API --> <activity - android:name=".crypto_provider.RegisterActivity" - android:label="TODO reg" + android:name="org.sufficientlysecure.keychain.remote_api.CryptoServiceActivity" + android:exported="false" + android:label="@string/app_name" android:process=":crypto" > - <intent-filter> - <action android:name="com.android.crypto.REGISTER" /> - <category android:name="android.intent.category.DEFAULT" /> - </intent-filter> + <!-- Don't publish intents, they are only used internally! --> </activity> + <activity + android:name="org.sufficientlysecure.keychain.remote_api.RegisteredAppsListActivity" + android:configChanges="orientation|screenSize|keyboardHidden|keyboard" + android:exported="false" + android:label="@string/title_crypto_consumers" /> + <activity + android:name="org.sufficientlysecure.keychain.remote_api.AppSettingsActivity" + android:configChanges="orientation|screenSize|keyboardHidden|keyboard" + android:exported="false" /> + + <!-- Remote API --> <service - android:name="org.sufficientlysecure.keychain.crypto_provider.CryptoService" + android:name="org.sufficientlysecure.keychain.remote_api.CryptoService" android:enabled="true" android:exported="true" android:process=":crypto" > <intent-filter> - <action android:name="com.android.crypto.ICryptoService" /> + <action android:name="org.openintents.crypto.ICryptoService" /> + </intent-filter> + <intent-filter> + + <!-- Can only be used from OpenPGP Keychain (internal): --> + <action android:name="org.sufficientlysecure.keychain.crypto_provider.IServiceActivityCallback" /> </intent-filter> <meta-data diff --git a/OpenPGP-Keychain/res/drawable-hdpi/ic_action_cancel.png b/OpenPGP-Keychain/res/drawable-hdpi/ic_action_cancel.png Binary files differnew file mode 100644 index 000000000..cde36e1fa --- /dev/null +++ b/OpenPGP-Keychain/res/drawable-hdpi/ic_action_cancel.png diff --git a/OpenPGP-Keychain/res/drawable-hdpi/ic_action_done.png b/OpenPGP-Keychain/res/drawable-hdpi/ic_action_done.png Binary files differnew file mode 100644 index 000000000..58bf97217 --- /dev/null +++ b/OpenPGP-Keychain/res/drawable-hdpi/ic_action_done.png diff --git a/OpenPGP-Keychain/res/drawable-mdpi/ic_action_cancel.png b/OpenPGP-Keychain/res/drawable-mdpi/ic_action_cancel.png Binary files differnew file mode 100644 index 000000000..9f4c3d6a2 --- /dev/null +++ b/OpenPGP-Keychain/res/drawable-mdpi/ic_action_cancel.png diff --git a/OpenPGP-Keychain/res/drawable-mdpi/ic_action_done.png b/OpenPGP-Keychain/res/drawable-mdpi/ic_action_done.png Binary files differnew file mode 100644 index 000000000..cf5fab3ad --- /dev/null +++ b/OpenPGP-Keychain/res/drawable-mdpi/ic_action_done.png diff --git a/OpenPGP-Keychain/res/drawable-xhdpi/ic_action_cancel.png b/OpenPGP-Keychain/res/drawable-xhdpi/ic_action_cancel.png Binary files differnew file mode 100644 index 000000000..ca7d159fd --- /dev/null +++ b/OpenPGP-Keychain/res/drawable-xhdpi/ic_action_cancel.png diff --git a/OpenPGP-Keychain/res/drawable-xhdpi/ic_action_done.png b/OpenPGP-Keychain/res/drawable-xhdpi/ic_action_done.png Binary files differnew file mode 100644 index 000000000..b8915716e --- /dev/null +++ b/OpenPGP-Keychain/res/drawable-xhdpi/ic_action_done.png diff --git a/OpenPGP-Keychain/res/layout/actionbar_custom_view_done.xml b/OpenPGP-Keychain/res/layout/actionbar_custom_view_done.xml new file mode 100644 index 000000000..bcbb5f5c1 --- /dev/null +++ b/OpenPGP-Keychain/res/layout/actionbar_custom_view_done.xml @@ -0,0 +1,27 @@ +<!-- + Copyright 2013 The Android Open Source Project + + 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:divider="@drawable/abs__list_divider_holo_light" + android:dividerPadding="12dp" + android:orientation="horizontal" + android:showDividers="end" > + + <include layout="@layout/actionbar_include_done_button" /> + +</LinearLayout>
\ No newline at end of file diff --git a/OpenPGP-Keychain/res/layout/actionbar_custom_view_done_cancel.xml b/OpenPGP-Keychain/res/layout/actionbar_custom_view_done_cancel.xml new file mode 100644 index 000000000..8c8428e75 --- /dev/null +++ b/OpenPGP-Keychain/res/layout/actionbar_custom_view_done_cancel.xml @@ -0,0 +1,29 @@ +<!-- + Copyright 2013 The Android Open Source Project + + 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:divider="@drawable/abs__list_divider_holo_light" + android:dividerPadding="12dp" + android:orientation="horizontal" + android:showDividers="middle" > + + <include layout="@layout/actionbar_include_cancel_button" /> + + <include layout="@layout/actionbar_include_done_button" /> + +</LinearLayout>
\ No newline at end of file diff --git a/OpenPGP-Keychain/res/layout/actionbar_include_cancel_button.xml b/OpenPGP-Keychain/res/layout/actionbar_include_cancel_button.xml new file mode 100644 index 000000000..0f0521d3c --- /dev/null +++ b/OpenPGP-Keychain/res/layout/actionbar_include_cancel_button.xml @@ -0,0 +1,36 @@ +<!-- + Copyright 2013 The Android Open Source Project + + 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. +--> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/actionbar_cancel" + style="@style/Widget.Sherlock.ActionButton" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" > + + <TextView + android:id="@+id/actionbar_cancel_text" + style="@style/Widget.Sherlock.ActionBar.TabText" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:drawableLeft="@drawable/ic_action_cancel" + android:drawablePadding="8dp" + android:gravity="center_vertical" + android:paddingRight="20dp" + android:text="Cancel (set in-code!)" /> + +</FrameLayout>
\ No newline at end of file diff --git a/OpenPGP-Keychain/res/layout/actionbar_include_done_button.xml b/OpenPGP-Keychain/res/layout/actionbar_include_done_button.xml new file mode 100644 index 000000000..54e5933e3 --- /dev/null +++ b/OpenPGP-Keychain/res/layout/actionbar_include_done_button.xml @@ -0,0 +1,36 @@ +<!-- + Copyright 2013 The Android Open Source Project + + 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. +--> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/actionbar_done" + style="@style/Widget.Sherlock.ActionButton" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" > + + <TextView + android:id="@+id/actionbar_done_text" + style="@style/Widget.Sherlock.ActionBar.TabText" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:drawableLeft="@drawable/ic_action_done" + android:drawablePadding="8dp" + android:gravity="center_vertical" + android:paddingRight="20dp" + android:text="Done (set in-code!)" /> + +</FrameLayout>
\ No newline at end of file diff --git a/OpenPGP-Keychain/res/layout/api_app_register_activity.xml b/OpenPGP-Keychain/res/layout/api_app_register_activity.xml new file mode 100644 index 000000000..40b8f54d1 --- /dev/null +++ b/OpenPGP-Keychain/res/layout/api_app_register_activity.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical" + android:padding="8dp" > + + <TextView + android:id="@+id/api_register_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingBottom="3dip" + android:text="@string/api_register_text" + android:textAppearance="?android:attr/textAppearanceLarge" /> + + <fragment + android:id="@+id/api_app_settings_fragment" + android:name="org.sufficientlysecure.keychain.remote_api.AppSettingsFragment" + android:layout_width="match_parent" + android:layout_height="wrap_content" + tools:layout="@layout/api_app_settings_fragment" /> + +</LinearLayout>
\ No newline at end of file diff --git a/OpenPGP-Keychain/res/layout/api_app_settings_activity.xml b/OpenPGP-Keychain/res/layout/api_app_settings_activity.xml new file mode 100644 index 000000000..c80d767ac --- /dev/null +++ b/OpenPGP-Keychain/res/layout/api_app_settings_activity.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical" + android:padding="8dp" > + + <fragment + android:id="@+id/api_app_settings_fragment" + android:name="org.sufficientlysecure.keychain.remote_api.AppSettingsFragment" + android:layout_width="match_parent" + android:layout_height="wrap_content" + tools:layout="@layout/api_app_settings_fragment" /> + +</LinearLayout>
\ No newline at end of file diff --git a/OpenPGP-Keychain/res/layout/api_app_settings_fragment.xml b/OpenPGP-Keychain/res/layout/api_app_settings_fragment.xml new file mode 100644 index 000000000..307da3efb --- /dev/null +++ b/OpenPGP-Keychain/res/layout/api_app_settings_fragment.xml @@ -0,0 +1,96 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical" > + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="?android:attr/listPreferredItemHeight" + android:gravity="center_horizontal" + android:orientation="horizontal" + android:paddingBottom="3dip" > + + <ImageView + android:id="@+id/api_app_settings_app_icon" + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_alignParentBottom="true" + android:layout_alignParentTop="true" + android:layout_marginRight="6dp" + android:src="@drawable/icon" /> + + <TextView + android:id="@+id/api_app_settings_app_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_toRightOf="@+id/api_app_settings_app_icon" + android:gravity="center_vertical" + android:orientation="vertical" + android:text="Name (set in-code)" + android:textAppearance="?android:attr/textAppearanceMedium" /> + </RelativeLayout> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" > + + <Button + android:id="@+id/api_app_settings_select_key_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:text="@string/api_settings_select_key" /> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingLeft="16dp" > + + <TextView + android:id="@+id/api_app_settings_user_id" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="right" + android:ellipsize="end" + android:singleLine="true" + android:text="@string/api_settings_no_key" + android:textAppearance="?android:attr/textAppearanceMedium" /> + + <TextView + android:id="@+id/api_app_settings_user_id_rest" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="right" + android:ellipsize="end" + android:singleLine="true" + android:text="" + android:textAppearance="?android:attr/textAppearanceSmall" /> + </LinearLayout> + </LinearLayout> + + <Button + android:id="@+id/api_app_settings_advanced_button" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/api_settings_show_advanced" /> + + <LinearLayout + android:id="@+id/api_app_settings_advanced" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:visibility="invisible" > + + <CheckBox + android:id="@+id/api_app_ascii_armor" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:text="@string/label_asciiArmour" /> + </LinearLayout> + +</LinearLayout>
\ No newline at end of file diff --git a/OpenPGP-Keychain/res/layout/api_apps_adapter_list_item.xml b/OpenPGP-Keychain/res/layout/api_apps_adapter_list_item.xml new file mode 100644 index 000000000..cb20a20af --- /dev/null +++ b/OpenPGP-Keychain/res/layout/api_apps_adapter_list_item.xml @@ -0,0 +1,28 @@ +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="?android:attr/listPreferredItemHeight" + android:gravity="left" + android:orientation="horizontal" > + + <ImageView + android:id="@+id/api_apps_adapter_item_icon" + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_alignParentBottom="true" + android:layout_alignParentTop="true" + android:layout_marginLeft="8dp" + android:layout_marginRight="8dp" + android:src="@drawable/icon" /> + + <TextView + android:id="@+id/api_apps_adapter_item_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_toRightOf="@+id/api_apps_adapter_item_icon" + android:gravity="center_vertical" + android:orientation="vertical" + android:text="Set in-code!" + android:textAppearance="?android:attr/textAppearanceMedium" /> + +</RelativeLayout>
\ No newline at end of file diff --git a/OpenPGP-Keychain/res/layout/api_apps_list_activity.xml b/OpenPGP-Keychain/res/layout/api_apps_list_activity.xml new file mode 100644 index 000000000..e4657c107 --- /dev/null +++ b/OpenPGP-Keychain/res/layout/api_apps_list_activity.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <fragment + android:id="@+id/crypto_consumers_list_fragment" + android:name="org.sufficientlysecure.keychain.remote_api.RegisteredAppsListFragment" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + +</LinearLayout>
\ No newline at end of file diff --git a/OpenPGP-Keychain/res/layout/decrypt.xml b/OpenPGP-Keychain/res/layout/decrypt.xml index 4901536ae..84f1fc298 100644 --- a/OpenPGP-Keychain/res/layout/decrypt.xml +++ b/OpenPGP-Keychain/res/layout/decrypt.xml @@ -22,7 +22,6 @@ android:orientation="vertical" > <ScrollView - xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:fillViewport="true" > diff --git a/OpenPGP-Keychain/res/layout/register_crypto_consumer_activity.xml b/OpenPGP-Keychain/res/layout/register_crypto_consumer_activity.xml deleted file mode 100644 index 24a64f1ac..000000000 --- a/OpenPGP-Keychain/res/layout/register_crypto_consumer_activity.xml +++ /dev/null @@ -1,25 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:orientation="vertical" > - - <TextView - android:id="@+id/textView1" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Register?" /> - - <Button - android:id="@+id/register_crypto_consumer_allow" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Allow access" /> - - <Button - android:id="@+id/register_crypto_consumer_disallow" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Disallow" /> - -</LinearLayout>
\ No newline at end of file diff --git a/OpenPGP-Keychain/res/menu/api_app_settings.xml b/OpenPGP-Keychain/res/menu/api_app_settings.xml new file mode 100644 index 000000000..1ee05f5de --- /dev/null +++ b/OpenPGP-Keychain/res/menu/api_app_settings.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android" > + + <item + android:id="@+id/menu_api_settings_revoke" + android:showAsAction="never" + android:title="@string/api_settings_revoke"/> + <item + android:id="@+id/menu_api_settings_cancel" + android:showAsAction="never" + android:title="@string/api_settings_cancel"/> + +</menu>
\ No newline at end of file diff --git a/OpenPGP-Keychain/res/values/strings.xml b/OpenPGP-Keychain/res/values/strings.xml index c698eab79..70f4993d7 100644 --- a/OpenPGP-Keychain/res/values/strings.xml +++ b/OpenPGP-Keychain/res/values/strings.xml @@ -30,6 +30,7 @@ <string name="title_createKey">Create Key</string> <string name="title_editKey">Edit Key</string> <string name="title_preferences">Preferences</string> + <string name="title_crypto_consumers">Registered Applications</string> <string name="title_keyServerPreference">Key Server Preference</string> <string name="title_changePassPhrase">Change Passphrase</string> <string name="title_setPassPhrase">Set Passphrase</string> @@ -87,6 +88,7 @@ <string name="menu_managePublicKeys">Manage Public Keys</string> <string name="menu_manageSecretKeys">Manage Secret Keys</string> <string name="menu_preferences">Settings</string> + <string name="menu_apiAppSettings">Registered Apps</string> <string name="menu_importFromFile">Import from file</string> <string name="menu_importFromQrCode">Import from QR Code</string> <string name="menu_importFromNfc">Import from NFC</string> @@ -362,4 +364,18 @@ <string name="intent_send_encrypt">OpenPGP: Encrypt</string> <string name="intent_send_decrypt">OpenPGP: Decrypt</string> + <!-- Remote API --> + <string name="api_no_apps">No registered applications!</string> + <string name="api_settings_show_advanced">Show advanced settings…</string> + <string name="api_settings_hide_advanced">Hide advanced settings…</string> + <string name="api_settings_no_key">No key selected</string> + <string name="api_settings_select_key">Select key</string> + <string name="api_settings_save">Save</string> + <string name="api_settings_cancel">Cancel</string> + <string name="api_settings_revoke">Revoke access</string> + <string name="api_register_text">The following application requests access to OpenPGP Keychain\'s API.\n\nAllow permanent access?</string> + <string name="api_register_allow">Allow access</string> + <string name="api_register_disallow">Disallow access</string> + <string name="api_register_error_select_key">Please select a key!</string> + </resources> diff --git a/OpenPGP-Keychain/src/org/openintents/crypto/CryptoError.aidl b/OpenPGP-Keychain/src/org/openintents/crypto/CryptoError.aidl new file mode 100644 index 000000000..7b67c8995 --- /dev/null +++ b/OpenPGP-Keychain/src/org/openintents/crypto/CryptoError.aidl @@ -0,0 +1,20 @@ +/* + * 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; + +// 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/org/openintents/crypto/CryptoError.java b/OpenPGP-Keychain/src/org/openintents/crypto/CryptoError.java new file mode 100644 index 000000000..265fe2633 --- /dev/null +++ b/OpenPGP-Keychain/src/org/openintents/crypto/CryptoError.java @@ -0,0 +1,76 @@ +/* + * 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 android.os.Parcel; +import android.os.Parcelable; + +public class CryptoError implements Parcelable { + int errorId; + String message; + + public CryptoError() { + } + + public CryptoError(int errorId, String message) { + this.errorId = errorId; + this.message = message; + } + + public CryptoError(CryptoError b) { + this.errorId = b.errorId; + this.message = b.message; + } + + public int getErrorId() { + return errorId; + } + + public void setErrorId(int errorId) { + this.errorId = errorId; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(errorId); + dest.writeString(message); + } + + public static final Creator<CryptoError> CREATOR = new Creator<CryptoError>() { + public CryptoError createFromParcel(final Parcel source) { + CryptoError error = new CryptoError(); + error.errorId = source.readInt(); + error.message = source.readString(); + return error; + } + + public CryptoError[] newArray(final int size) { + return new CryptoError[size]; + } + }; +} diff --git a/OpenPGP-Keychain/src/org/openintents/crypto/CryptoServiceConnection.java b/OpenPGP-Keychain/src/org/openintents/crypto/CryptoServiceConnection.java new file mode 100644 index 000000000..d9e91f772 --- /dev/null +++ b/OpenPGP-Keychain/src/org/openintents/crypto/CryptoServiceConnection.java @@ -0,0 +1,91 @@ +/* + * 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; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.util.Log; + +public class CryptoServiceConnection { + private Context mApplicationContext; + + private ICryptoService mService; + private boolean bound; + private String cryptoProviderPackageName; + + private static final String TAG = "CryptoConnection"; + + public CryptoServiceConnection(Context context, String cryptoProviderPackageName) { + mApplicationContext = context.getApplicationContext(); + this.cryptoProviderPackageName = cryptoProviderPackageName; + } + + public ICryptoService getService() { + return mService; + } + + private ServiceConnection mCryptoServiceConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName name, IBinder service) { + mService = ICryptoService.Stub.asInterface(service); + Log.d(TAG, "connected to service"); + bound = true; + } + + public void onServiceDisconnected(ComponentName name) { + mService = null; + Log.d(TAG, "disconnected from service"); + bound = false; + } + }; + + /** + * If not already bound, bind! + * + * @return + */ + public boolean bindToService() { + if (mService == null && !bound) { // if not already connected + try { + Log.d(TAG, "not bound yet"); + + Intent serviceIntent = new Intent(); + serviceIntent.setAction("org.openintents.crypto.ICryptoService"); + serviceIntent.setPackage(cryptoProviderPackageName); + mApplicationContext.bindService(serviceIntent, mCryptoServiceConnection, + Context.BIND_AUTO_CREATE); + + return true; + } catch (Exception e) { + Log.d(TAG, "Exception", e); + return false; + } + } else { // already connected + Log.d(TAG, "already bound... "); + return true; + } + } + + public void unbindFromService() { + mApplicationContext.unbindService(mCryptoServiceConnection); + } + +} diff --git a/OpenPGP-Keychain/src/org/openintents/crypto/CryptoSignatureResult.aidl b/OpenPGP-Keychain/src/org/openintents/crypto/CryptoSignatureResult.aidl new file mode 100644 index 000000000..1d39bac70 --- /dev/null +++ b/OpenPGP-Keychain/src/org/openintents/crypto/CryptoSignatureResult.aidl @@ -0,0 +1,20 @@ +/* + * 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; + +// 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/org/openintents/crypto/CryptoSignatureResult.java b/OpenPGP-Keychain/src/org/openintents/crypto/CryptoSignatureResult.java new file mode 100644 index 000000000..e193b73b3 --- /dev/null +++ b/OpenPGP-Keychain/src/org/openintents/crypto/CryptoSignatureResult.java @@ -0,0 +1,76 @@ +/* + * 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 android.os.Parcel; +import android.os.Parcelable; + +public class CryptoSignatureResult implements Parcelable { + String signatureUserId; + + boolean signature; + boolean signatureSuccess; + boolean signatureUnknown; + + public CryptoSignatureResult() { + + } + + public CryptoSignatureResult(String signatureUserId, boolean signature, + boolean signatureSuccess, boolean signatureUnknown) { + this.signatureUserId = signatureUserId; + + this.signature = signature; + this.signatureSuccess = signatureSuccess; + this.signatureUnknown = signatureUnknown; + } + + public CryptoSignatureResult(CryptoSignatureResult b) { + this.signatureUserId = b.signatureUserId; + + this.signature = b.signature; + this.signatureSuccess = b.signatureSuccess; + this.signatureUnknown = b.signatureUnknown; + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(signatureUserId); + + dest.writeByte((byte) (signature ? 1 : 0)); + dest.writeByte((byte) (signatureSuccess ? 1 : 0)); + dest.writeByte((byte) (signatureUnknown ? 1 : 0)); + } + + public static final Creator<CryptoSignatureResult> CREATOR = new Creator<CryptoSignatureResult>() { + public CryptoSignatureResult createFromParcel(final Parcel source) { + CryptoSignatureResult vr = new CryptoSignatureResult(); + vr.signatureUserId = source.readString(); + vr.signature = source.readByte() == 1; + vr.signatureSuccess = source.readByte() == 1; + vr.signatureUnknown = source.readByte() == 1; + return vr; + } + + public CryptoSignatureResult[] newArray(final int size) { + return new CryptoSignatureResult[size]; + } + }; +} diff --git a/OpenPGP-Keychain/src/org/openintents/crypto/ICryptoCallback.aidl b/OpenPGP-Keychain/src/org/openintents/crypto/ICryptoCallback.aidl new file mode 100644 index 000000000..1f910d4c6 --- /dev/null +++ b/OpenPGP-Keychain/src/org/openintents/crypto/ICryptoCallback.aidl @@ -0,0 +1,32 @@ +/* + * 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.CryptoSignatureResult; +import org.openintents.crypto.CryptoError; + +interface ICryptoCallback { + + /** + * 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); +}
\ No newline at end of file diff --git a/OpenPGP-Keychain/src/org/openintents/crypto/ICryptoService.aidl b/OpenPGP-Keychain/src/org/openintents/crypto/ICryptoService.aidl new file mode 100644 index 000000000..c84ca28fb --- /dev/null +++ b/OpenPGP-Keychain/src/org/openintents/crypto/ICryptoService.aidl @@ -0,0 +1,82 @@ +/* + * 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.ICryptoCallback; + +/** + * All methods are oneway, which means they are asynchronous and non-blocking. + * Results are returned to the callback, which has to be implemented on client side. + */ +interface ICryptoService { + + /** + * Encrypt + * + * @param inputBytes + * Byte array you want to encrypt + * @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); + + /** + * Sign + * + * @param inputBytes + * Byte array you want to encrypt + * @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); + + /** + * Encrypt and sign + * + * @param inputBytes + * Byte array you want to encrypt + * @param encryptionUserIds + * User Ids (emails) of recipients + * @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); + + /** + * Decrypts and verifies given input bytes. If no signature is present this method + * will only decrypt. + * + * @param inputBytes + * Byte array you want to decrypt and verify + * @param callback + * Callback where to return results + */ + oneway void decryptAndVerify(in byte[] inputBytes, in ICryptoCallback callback); + + /** + * Opens setup using default parameters + * + */ + 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 @@ -68,72 +68,6 @@ See http://docs.oseems.com/general/application/eclipse/fix-gc-overhead-limit-exc 1. Open svg file in Inkscape 2. Extensions -> Color -> darker (2 times!) -# Security Model - -## Basic goals - -* Intents without permissions should only work based on user interaction (e.g. click a button in a dialog) - -Android primitives to exchange data: Intent, Intent with return values, Send (also an Intent), Content Provider, AIDL - -## Possible Permissions - -* ACCESS_API: Encrypt/Sign/Decrypt/Create keys without user interaction (intents, remote service), Read key information (not the actual keys)(content provider) -* ACCESS_KEYS: get and import actual public and secret keys (remote service) - -## Without Permissions - -### Intents -All Intents start with org.sufficientlysecure.keychain.action. - -* android.intent.action.VIEW connected to .gpg and .asc files: Import Key and Decrypt -* android.intent.action.SEND connected to all mime types (text/plain and every binary data like files and images): Encrypt and Decrypt -* IMPORT -* IMPORT_FROM_FILE -* IMPORT_FROM_QR_CODE -* IMPORT_FROM_NFC -* SHARE_KEYRING -* SHARE_KEYRING_WITH_QR_CODE -* SHARE_KEYRING_WITH_NFC -* EDIT_KEYRING -* SELECT_PUBLIC_KEYRINGS -* SELECT_SECRET_KEYRING -* ENCRYPT -* ENCRYPT_FILE -* DECRYPT -* DECRYPT_FILE - -## With permission ACCESS_API - -### Intents - -* CREATE_KEYRING -* ENCRYPT_AND_RETURN -* ENCRYPT_STREAM_AND_RETURN -* GENERATE_SIGNATURE_AND_RETURN -* DECRYPT_AND_RETURN -* DECRYPT_STREAM_AND_RETURN - -### Broadcast Receiver -On change of database the following broadcast is send. -* DATABASE_CHANGE - -### Content Provider - -* The whole content provider requires a permission (only read) -* Don't give out blobs (keys can be accessed by ACCESS_KEYS via remote service) -* Make an internal and external content provider (or pathes with <path-permission>) -* Look at android:grantUriPermissions especially for ApgServiceBlobProvider -* Only give out android:readPermission - -### ApgApiService (Remote Service) -AIDL service - -## With permission ACCESS_KEYS - -### ApgKeyService (Remote Service) -AIDL service to access actual private keyring objects - # Coding Style ## Code |