diff options
Diffstat (limited to 'OpenPGP-Keychain/src/org/sufficientlysecure/keychain/openpgp_api')
8 files changed, 1813 insertions, 0 deletions
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/openpgp_api/AppSettings.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/openpgp_api/AppSettings.java new file mode 100644 index 000000000..982d5fe40 --- /dev/null +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/openpgp_api/AppSettings.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.openpgp_api; + +import org.spongycastle.bcpg.HashAlgorithmTags; +import org.spongycastle.openpgp.PGPEncryptedData; +import org.sufficientlysecure.keychain.Id; + +public class AppSettings { + private String packageName; + private long keyId = Id.key.none; + private int encryptionAlgorithm; + private int hashAlgorithm; + private int compression; + + public AppSettings() { + + } + + public AppSettings(String packageName) { + super(); + this.packageName = packageName; + // defaults: + this.encryptionAlgorithm = PGPEncryptedData.AES_128; + this.hashAlgorithm = HashAlgorithmTags.SHA256; + this.compression = Id.choice.compression.zlib; + } + + 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 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/openpgp_api/AppSettingsActivity.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/openpgp_api/AppSettingsActivity.java new file mode 100644 index 000000000..030a2bbb8 --- /dev/null +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/openpgp_api/AppSettingsActivity.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.openpgp_api; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.helper.ActionBarHelper; +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.View; + +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. + ActionBarHelper.setDoneView(getSupportActionBar(), R.string.api_settings_save, + new View.OnClickListener() { + @Override + public void onClick(View v) { + // "Done" + save(); + } + }); + + 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/openpgp_api/AppSettingsFragment.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/openpgp_api/AppSettingsFragment.java new file mode 100644 index 000000000..ad463d316 --- /dev/null +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/openpgp_api/AppSettingsFragment.java @@ -0,0 +1,304 @@ +/* + * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.openpgp_api; + +import java.util.HashMap; + +import org.spongycastle.bcpg.HashAlgorithmTags; +import org.spongycastle.openpgp.PGPEncryptedData; +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.KeyValueSpinnerAdapter; +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.AdapterView; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.Spinner; +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 Spinner mEncryptionAlgorithm; + private Spinner mHashAlgorithm; + private Spinner mCompression; + + KeyValueSpinnerAdapter encryptionAdapter; + KeyValueSpinnerAdapter hashAdapter; + KeyValueSpinnerAdapter compressionAdapter; + + public AppSettings getAppSettings() { + return appSettings; + } + + public void setAppSettings(AppSettings appSettings) { + this.appSettings = appSettings; + setPackage(appSettings.getPackageName()); + updateSelectedKeyView(appSettings.getKeyId()); + mEncryptionAlgorithm.setSelection(encryptionAdapter.getPosition(appSettings + .getEncryptionAlgorithm())); + mHashAlgorithm.setSelection(hashAdapter.getPosition(appSettings.getHashAlgorithm())); + mCompression.setSelection(compressionAdapter.getPosition(appSettings.getCompression())); + } + + /** + * 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); + mEncryptionAlgorithm = (Spinner) view + .findViewById(R.id.api_app_settings_encryption_algorithm); + mHashAlgorithm = (Spinner) view.findViewById(R.id.api_app_settings_hash_algorithm); + mCompression = (Spinner) view.findViewById(R.id.api_app_settings_compression); + + HashMap<Integer, String> encryptionMap = new HashMap<Integer, String>(); + encryptionMap.put(PGPEncryptedData.AES_128, "AES-128"); + encryptionMap.put(PGPEncryptedData.AES_192, "AES-192"); + encryptionMap.put(PGPEncryptedData.AES_256, "AES-256"); + encryptionMap.put(PGPEncryptedData.BLOWFISH, "Blowfish"); + encryptionMap.put(PGPEncryptedData.TWOFISH, "Twofish"); + encryptionMap.put(PGPEncryptedData.CAST5, "CAST5"); + encryptionMap.put(PGPEncryptedData.DES, "DES"); + encryptionMap.put(PGPEncryptedData.TRIPLE_DES, "Triple DES"); + encryptionMap.put(PGPEncryptedData.IDEA, "IDEA"); + + encryptionAdapter = new KeyValueSpinnerAdapter(getActivity(), encryptionMap); + mEncryptionAlgorithm.setAdapter(encryptionAdapter); + mEncryptionAlgorithm.setOnItemSelectedListener(new OnItemSelectedListener() { + + @Override + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { + appSettings.setEncryptionAlgorithm((int) id); + } + + @Override + public void onNothingSelected(AdapterView<?> parent) { + } + }); + + HashMap<Integer, String> hashMap = new HashMap<Integer, String>(); + hashMap.put(HashAlgorithmTags.MD5, "MD5"); + hashMap.put(HashAlgorithmTags.RIPEMD160, "RIPEMD-160"); + hashMap.put(HashAlgorithmTags.SHA1, "SHA-1"); + hashMap.put(HashAlgorithmTags.SHA224, "SHA-224"); + hashMap.put(HashAlgorithmTags.SHA256, "SHA-256"); + hashMap.put(HashAlgorithmTags.SHA384, "SHA-384"); + hashMap.put(HashAlgorithmTags.SHA512, "SHA-512"); + + hashAdapter = new KeyValueSpinnerAdapter(getActivity(), hashMap); + mHashAlgorithm.setAdapter(hashAdapter); + mHashAlgorithm.setOnItemSelectedListener(new OnItemSelectedListener() { + + @Override + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { + appSettings.setHashAlgorithm((int) id); + } + + @Override + public void onNothingSelected(AdapterView<?> parent) { + } + }); + + HashMap<Integer, String> compressionMap = new HashMap<Integer, String>(); + compressionMap.put(Id.choice.compression.none, getString(R.string.choice_none) + " (" + + getString(R.string.fast) + ")"); + compressionMap.put(Id.choice.compression.zip, "ZIP (" + getString(R.string.fast) + ")"); + compressionMap.put(Id.choice.compression.zlib, "ZLIB (" + getString(R.string.fast) + ")"); + compressionMap.put(Id.choice.compression.bzip2, "BZIP2 (" + getString(R.string.very_slow) + + ")"); + + compressionAdapter = new KeyValueSpinnerAdapter(getActivity(), compressionMap); + mCompression.setAdapter(compressionAdapter); + mCompression.setOnItemSelectedListener(new OnItemSelectedListener() { + + @Override + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { + appSettings.setCompression((int) id); + } + + @Override + public void onNothingSelected(AdapterView<?> parent) { + } + }); + + mSelectKeyButton.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + selectSecretKey(); + } + }); + + 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/openpgp_api/OpenPgpService.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/openpgp_api/OpenPgpService.java new file mode 100644 index 000000000..beefa56b4 --- /dev/null +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/openpgp_api/OpenPgpService.java @@ -0,0 +1,747 @@ +/* + * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.openpgp_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.regex.Matcher; + +import org.openintents.openpgp.IOpenPgpCallback; +import org.openintents.openpgp.IOpenPgpService; +import org.openintents.openpgp.OpenPgpError; +import org.openintents.openpgp.OpenPgpSignatureResult; +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.helper.Preferences; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.PassphraseCacheService; +import org.sufficientlysecure.keychain.util.InputData; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.PausableThreadPoolExecutor; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; + +public class OpenPgpService extends Service { + Context mContext; + + final ArrayBlockingQueue<Runnable> mPoolQueue = new ArrayBlockingQueue<Runnable>(100); + // TODO: Are these parameters okay? + PausableThreadPoolExecutor mThreadPool = new PausableThreadPoolExecutor(2, 4, 10, + TimeUnit.SECONDS, mPoolQueue); + + final Object userInputLock = new Object(); + + private class MyBaseCallback implements Handler.Callback { + public static final int OKAY = 1; + public static final int CANCEL = 0; + + @Override + public boolean handleMessage(Message msg) { + return false; + } + + } + + @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 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(OpenPgpServiceActivity.EXTRA_SECRET_KEY_ID, keyId); + + PassphraseActivityCallback callback = new PassphraseActivityCallback(); + Messenger messenger = new Messenger(new Handler(getMainLooper(), callback)); + + pauseQueueAndStartServiceActivity(OpenPgpServiceActivity.ACTION_CACHE_PASSPHRASE, + messenger, extras); + + if (callback.isSuccess()) { + Log.d(Constants.TAG, "New passphrase entered!"); + + // get again after it was entered + passphrase = PassphraseCacheService.getCachedPassphrase(mContext, keyId); + } else { + Log.d(Constants.TAG, "Passphrase dialog canceled!"); + + return null; + } + + } + + return passphrase; + } + + public class PassphraseActivityCallback extends MyBaseCallback { + + private boolean success = false; + + public boolean isSuccess() { + return success; + } + + @Override + public boolean handleMessage(Message msg) { + if (msg.arg1 == OKAY) { + success = true; + } else { + success = false; + } + + // resume + synchronized (userInputLock) { + userInputLock.notifyAll(); + } + mThreadPool.resume(); + return true; + } + }; + + /** + * Search database for key ids based on emails. + * + * @param encryptionUserIds + * @return + */ + private long[] getKeyIdsFromEmails(String[] encryptionUserIds, long ownKeyId) { + // find key ids to given emails in database + ArrayList<Long> keyIds = new ArrayList<Long>(); + + boolean missingUserIdsCheck = false; + boolean dublicateUserIdsCheck = false; + ArrayList<String> missingUserIds = new ArrayList<String>(); + ArrayList<String> dublicateUserIds = new ArrayList<String>(); + + for (String email : encryptionUserIds) { + Uri uri = KeychainContract.KeyRings.buildPublicKeyRingsByEmailsUri(email); + Cursor cur = getContentResolver().query(uri, null, null, null, null); + if (cur.moveToFirst()) { + long id = cur.getLong(cur.getColumnIndex(KeychainContract.KeyRings.MASTER_KEY_ID)); + keyIds.add(id); + } else { + missingUserIdsCheck = true; + missingUserIds.add(email); + Log.d(Constants.TAG, "user id missing"); + } + if (cur.moveToNext()) { + dublicateUserIdsCheck = true; + dublicateUserIds.add(email); + Log.d(Constants.TAG, "more than one user id with the same email"); + } + } + + // also encrypt to our self (so that we can decrypt it later!) + keyIds.add(ownKeyId); + + // convert to long[] + long[] keyIdsArray = new long[keyIds.size()]; + for (int i = 0; i < keyIdsArray.length; i++) { + keyIdsArray[i] = keyIds.get(i); + } + + if (missingUserIdsCheck || dublicateUserIdsCheck) { + SelectPubKeysActivityCallback callback = new SelectPubKeysActivityCallback(); + Messenger messenger = new Messenger(new Handler(getMainLooper(), callback)); + + Bundle extras = new Bundle(); + extras.putLongArray(OpenPgpServiceActivity.EXTRA_SELECTED_MASTER_KEY_IDS, keyIdsArray); + extras.putStringArrayList(OpenPgpServiceActivity.EXTRA_MISSING_USER_IDS, missingUserIds); + extras.putStringArrayList(OpenPgpServiceActivity.EXTRA_DUBLICATE_USER_IDS, + dublicateUserIds); + + pauseQueueAndStartServiceActivity(OpenPgpServiceActivity.ACTION_SELECT_PUB_KEYS, + messenger, extras); + + if (callback.isSuccess()) { + Log.d(Constants.TAG, "New selection of pub keys!"); + keyIdsArray = callback.getPubKeyIds(); + } else { + Log.d(Constants.TAG, "Pub key selection canceled!"); + return null; + } + } + + if (keyIdsArray.length == 0) { + return null; + } + return keyIdsArray; + } + + public class SelectPubKeysActivityCallback extends MyBaseCallback { + public static final String PUB_KEY_IDS = "pub_key_ids"; + + private boolean success = false; + private long[] pubKeyIds; + + public boolean isSuccess() { + return success; + } + + public long[] getPubKeyIds() { + return pubKeyIds; + } + + @Override + public boolean handleMessage(Message msg) { + if (msg.arg1 == OKAY) { + success = true; + pubKeyIds = msg.getData().getLongArray(PUB_KEY_IDS); + } else { + success = false; + } + + // resume + synchronized (userInputLock) { + userInputLock.notifyAll(); + } + mThreadPool.resume(); + return true; + } + }; + + private synchronized void encryptAndSignSafe(byte[] inputBytes, String[] encryptionUserIds, + boolean asciiArmor, IOpenPgpCallback callback, AppSettings appSettings, boolean sign) + 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[] keyIds = getKeyIdsFromEmails(encryptionUserIds, appSettings.getKeyId()); + if (keyIds == null) { + callback.onError(new OpenPgpError(OpenPgpError.ID_NO_USER_IDS, "No user ids!")); + return; + } + + if (sign) { + String passphrase = getCachedPassphrase(appSettings.getKeyId()); + if (passphrase == null) { + callback.onError(new OpenPgpError(OpenPgpError.ID_NO_OR_WRONG_PASSPHRASE, + "No or wrong passphrase!")); + return; + } + + PgpMain.encryptAndSign(mContext, null, inputData, outputStream, asciiArmor, + appSettings.getCompression(), keyIds, null, + appSettings.getEncryptionAlgorithm(), appSettings.getKeyId(), + appSettings.getHashAlgorithm(), true, passphrase); + } else { + PgpMain.encryptAndSign(mContext, null, inputData, outputStream, asciiArmor, + appSettings.getCompression(), keyIds, null, + appSettings.getEncryptionAlgorithm(), Id.key.none, + appSettings.getHashAlgorithm(), true, null); + } + + 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 OpenPgpError(0, e.getMessage())); + } catch (Exception t) { + Log.e(Constants.TAG, "Error returning exception to client", t); + } + } + } + + // TODO: asciiArmor?! + private void signSafe(byte[] inputBytes, IOpenPgpCallback callback, AppSettings appSettings) + throws RemoteException { + try { + Log.d(Constants.TAG, "current therad id: " + Thread.currentThread().getId()); + + // 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()); + if (passphrase == null) { + callback.onError(new OpenPgpError(OpenPgpError.ID_NO_OR_WRONG_PASSPHRASE, + "No or wrong passphrase!")); + return; + } + + PgpMain.signText(this, null, inputData, outputStream, appSettings.getKeyId(), + passphrase, appSettings.getHashAlgorithm(), Preferences.getPreferences(this) + .getForceV3Signatures()); + + 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 OpenPgpError(0, e.getMessage())); + } catch (Exception t) { + Log.e(Constants.TAG, "Error returning exception to client", t); + } + } + } + + private synchronized void decryptAndVerifySafe(byte[] inputBytes, IOpenPgpCallback callback, + AppSettings appSettings) throws RemoteException { + try { + // TODO: this is not really needed + // checked if it is text with BEGIN and END tags + String message = new String(inputBytes); + Log.d(Constants.TAG, "in: " + message); + boolean signedOnly = false; + Matcher matcher = PgpMain.PGP_MESSAGE.matcher(message); + if (matcher.matches()) { + Log.d(Constants.TAG, "PGP_MESSAGE matched"); + message = matcher.group(1); + // replace non breakable spaces + message = message.replaceAll("\\xa0", " "); + + // overwrite inputBytes + inputBytes = message.getBytes(); + } else { + matcher = PgpMain.PGP_SIGNED_MESSAGE.matcher(message); + if (matcher.matches()) { + signedOnly = true; + Log.d(Constants.TAG, "PGP_SIGNED_MESSAGE matched"); + message = matcher.group(1); + // replace non breakable spaces + message = message.replaceAll("\\xa0", " "); + + // overwrite inputBytes + inputBytes = message.getBytes(); + } else { + Log.d(Constants.TAG, "Nothing matched! Binary?"); + } + } + // END TODO + + Log.d(Constants.TAG, "in: " + new String(inputBytes)); + + // 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)); + // } + + String passphrase = null; + boolean assumeSymmetricEncryption = false; + if (!signedOnly) { + // BEGIN Get key + // TODO: this input stream is consumed after PgpMain.getDecryptionKeyId()... do it + // better! + InputStream inputStream2 = new ByteArrayInputStream(inputBytes); + + // TODO: duplicates functions from DecryptActivity! + // TODO: we need activity to input symmetric passphrase + long secretKeyId; + try { + if (inputStream2.markSupported()) { + inputStream2.mark(200); // should probably set this to the max size of two + // pgpF + // objects, if it even needs to be anything other + // than + // 0. + } + secretKeyId = PgpMain.getDecryptionKeyId(this, inputStream2); + if (secretKeyId == Id.key.none) { + throw new PgpMain.PgpGeneralException( + getString(R.string.error_noSecretKeyFound)); + } + assumeSymmetricEncryption = false; + } catch (PgpMain.NoAsymmetricEncryptionException e) { + if (inputStream2.markSupported()) { + inputStream2.reset(); + } + secretKeyId = Id.key.symmetric; + if (!PgpMain.hasSymmetricEncryption(this, inputStream2)) { + throw new PgpMain.PgpGeneralException( + getString(R.string.error_noKnownEncryptionFound)); + } + assumeSymmetricEncryption = true; + } + + Log.d(Constants.TAG, "secretKeyId " + secretKeyId); + + passphrase = getCachedPassphrase(secretKeyId); + if (passphrase == null) { + callback.onError(new OpenPgpError(OpenPgpError.ID_NO_OR_WRONG_PASSPHRASE, + "No or wrong passphrase!")); + return; + } + } + + // 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(); + + Bundle outputBundle; + if (signedOnly) { + // TODO: download missing keys from keyserver? + outputBundle = PgpMain.verifyText(this, null, inputData, outputStream, false); + } else { + // TODO: assume symmetric: callback to enter symmetric pass + outputBundle = PgpMain.decryptAndVerify(this, null, inputData, outputStream, + passphrase, assumeSymmetricEncryption); + } + + 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); + + OpenPgpSignatureResult sigResult = null; + if (signature) { + sigResult = new OpenPgpSignatureResult(signatureUserId, 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 OpenPgpError(0, e.getMessage())); + } catch (Exception t) { + Log.e(Constants.TAG, "Error returning exception to client", t); + } + } + } + + private final IOpenPgpService.Stub mBinder = new IOpenPgpService.Stub() { + + @Override + public void encrypt(final byte[] inputBytes, final String[] encryptionUserIds, + final boolean asciiArmor, final IOpenPgpCallback callback) throws RemoteException { + + final AppSettings settings = getAppSettings(); + + Runnable r = new Runnable() { + + @Override + public void run() { + try { + encryptAndSignSafe(inputBytes, encryptionUserIds, asciiArmor, callback, + settings, false); + } catch (RemoteException e) { + Log.e(Constants.TAG, "CryptoService", e); + } + } + }; + + checkAndEnqueue(r); + } + + @Override + public void encryptAndSign(final byte[] inputBytes, final String[] encryptionUserIds, + final boolean asciiArmor, final IOpenPgpCallback callback) throws RemoteException { + + final AppSettings settings = getAppSettings(); + + Runnable r = new Runnable() { + + @Override + public void run() { + try { + encryptAndSignSafe(inputBytes, encryptionUserIds, asciiArmor, callback, + settings, true); + } catch (RemoteException e) { + Log.e(Constants.TAG, "CryptoService", e); + } + } + }; + + checkAndEnqueue(r); + } + + @Override + public void sign(final byte[] inputBytes, boolean asciiArmor, + final IOpenPgpCallback callback) throws RemoteException { + final AppSettings settings = getAppSettings(); + + Runnable r = new Runnable() { + + @Override + public void run() { + try { + signSafe(inputBytes, callback, settings); + } catch (RemoteException e) { + Log.e(Constants.TAG, "CryptoService", e); + } + } + }; + + checkAndEnqueue(r); + + } + + @Override + public void decryptAndVerify(final byte[] inputBytes, final IOpenPgpCallback callback) + throws RemoteException { + + final AppSettings settings = getAppSettings(); + + Runnable r = new Runnable() { + + @Override + public void run() { + try { + decryptAndVerifySafe(inputBytes, callback, settings); + } catch (RemoteException e) { + Log.e(Constants.TAG, "CryptoService", e); + } + } + }; + + checkAndEnqueue(r); + } + + }; + + 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(OpenPgpServiceActivity.EXTRA_PACKAGE_NAME, callingPackages[0]); + + RegisterActivityCallback callback = new RegisterActivityCallback(); + Messenger messenger = new Messenger(new Handler(getMainLooper(), callback)); + + pauseQueueAndStartServiceActivity(OpenPgpServiceActivity.ACTION_REGISTER, messenger, + extras); + + if (callback.isAllowed()) { + mThreadPool.execute(r); + Log.d(Constants.TAG, "Enqueued runnable…"); + } else { + Log.d(Constants.TAG, "User disallowed app!"); + } + } + } + + public class RegisterActivityCallback extends MyBaseCallback { + public static final String PACKAGE_NAME = "package_name"; + + private boolean allowed = false; + private String packageName; + + public boolean isAllowed() { + return allowed; + } + + public String getPackageName() { + return packageName; + } + + @Override + public boolean handleMessage(Message msg) { + if (msg.arg1 == OKAY) { + allowed = true; + packageName = msg.getData().getString(PACKAGE_NAME); + + // resume threads + if (isPackageAllowed(packageName, false)) { + synchronized (userInputLock) { + userInputLock.notifyAll(); + } + mThreadPool.resume(); + } else { + // Should not happen! + Log.e(Constants.TAG, "Should not happen! Emergency shutdown!"); + mThreadPool.shutdownNow(); + } + } else { + allowed = false; + + synchronized (userInputLock) { + userInputLock.notifyAll(); + } + mThreadPool.resume(); + } + return true; + } + + } + + /** + * 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, Messenger messenger, Bundle extras) { + synchronized (userInputLock) { + mThreadPool.pause(); + + Log.d(Constants.TAG, "starting activity..."); + Intent intent = new Intent(getBaseContext(), OpenPgpServiceActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setAction(action); + + extras.putParcelable(OpenPgpServiceActivity.EXTRA_MESSENGER, messenger); + intent.putExtras(extras); + + startActivity(intent); + + // lock current thread for user input + try { + userInputLock.wait(); + } catch (InterruptedException e) { + Log.e(Constants.TAG, "CryptoService", e); + } + } + + } +} diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/openpgp_api/OpenPgpServiceActivity.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/openpgp_api/OpenPgpServiceActivity.java new file mode 100644 index 000000000..775828124 --- /dev/null +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/openpgp_api/OpenPgpServiceActivity.java @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.openpgp_api; + +import java.util.ArrayList; + +import org.sufficientlysecure.htmltextview.HtmlTextView; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.Id; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.helper.ActionBarHelper; +import org.sufficientlysecure.keychain.helper.PgpMain; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.ui.SelectPublicKeyFragment; +import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; +import org.sufficientlysecure.keychain.util.Log; + +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.view.View; +import android.widget.Toast; + +import com.actionbarsherlock.app.SherlockFragmentActivity; + +public class OpenPgpServiceActivity extends SherlockFragmentActivity { + + public static final String ACTION_REGISTER = Constants.INTENT_PREFIX + "API_ACTIVITY_REGISTER"; + public static final String ACTION_CACHE_PASSPHRASE = Constants.INTENT_PREFIX + + "API_ACTIVITY_CACHE_PASSPHRASE"; + public static final String ACTION_SELECT_PUB_KEYS = Constants.INTENT_PREFIX + + "API_ACTIVITY_SELECT_PUB_KEYS"; + + public static final String EXTRA_MESSENGER = "messenger"; + + // passphrase action + public static final String EXTRA_SECRET_KEY_ID = "secret_key_id"; + // register action + public static final String EXTRA_PACKAGE_NAME = "package_name"; + // select pub keys action + public static final String EXTRA_SELECTED_MASTER_KEY_IDS = "master_key_ids"; + public static final String EXTRA_MISSING_USER_IDS = "missing_user_ids"; + public static final String EXTRA_DUBLICATE_USER_IDS = "dublicate_user_ids"; + + private Messenger mMessenger; + + // register view + private AppSettingsFragment mSettingsFragment; + // select pub keys view + private SelectPublicKeyFragment mSelectFragment; + + // has the user clicked one of the buttons + // or do we need to handle the callback in onStop() + private boolean finishHandled; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + handleActions(getIntent(), savedInstanceState); + } + + @Override + protected void onStop() { + super.onStop(); + + if (!finishHandled) { + Message msg = Message.obtain(); + msg.arg1 = OpenPgpService.RegisterActivityCallback.CANCEL; + try { + mMessenger.send(msg); + } catch (RemoteException e) { + Log.e(Constants.TAG, "CryptoServiceActivity", e); + } + } + } + + protected void handleActions(Intent intent, Bundle savedInstanceState) { + finishHandled = false; + + String action = intent.getAction(); + Bundle extras = intent.getExtras(); + + if (extras == null) { + extras = new Bundle(); + } + + mMessenger = extras.getParcelable(EXTRA_MESSENGER); + + /** + * 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 + ActionBarHelper.setDoneCancelView(getSupportActionBar(), R.string.api_register_allow, + new View.OnClickListener() { + @Override + public void onClick(View v) { + // Allow + + // user needs to select a key! + if (mSettingsFragment.getAppSettings().getKeyId() == Id.key.none) { + Toast.makeText(OpenPgpServiceActivity.this, + R.string.api_register_error_select_key, Toast.LENGTH_LONG) + .show(); + } else { + ProviderHelper.insertApiApp(OpenPgpServiceActivity.this, + mSettingsFragment.getAppSettings()); + + Message msg = Message.obtain(); + msg.arg1 = OpenPgpService.RegisterActivityCallback.OKAY; + Bundle data = new Bundle(); + data.putString(OpenPgpService.RegisterActivityCallback.PACKAGE_NAME, + packageName); + msg.setData(data); + try { + mMessenger.send(msg); + } catch (RemoteException e) { + Log.e(Constants.TAG, "CryptoServiceActivity", e); + } + + finishHandled = true; + finish(); + } + } + }, R.string.api_register_disallow, new View.OnClickListener() { + @Override + public void onClick(View v) { + // Disallow + + Message msg = Message.obtain(); + msg.arg1 = OpenPgpService.RegisterActivityCallback.CANCEL; + try { + mMessenger.send(msg); + } catch (RemoteException e) { + Log.e(Constants.TAG, "CryptoServiceActivity", e); + } + + finishHandled = true; + finish(); + } + }); + + setContentView(R.layout.api_app_register_activity); + + mSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById( + R.id.api_app_settings_fragment); + + AppSettings settings = new AppSettings(packageName); + mSettingsFragment.setAppSettings(settings); + } else if (ACTION_CACHE_PASSPHRASE.equals(action)) { + long secretKeyId = extras.getLong(EXTRA_SECRET_KEY_ID); + + showPassphraseDialog(secretKeyId); + } else if (ACTION_SELECT_PUB_KEYS.equals(action)) { + long[] selectedMasterKeyIds = intent.getLongArrayExtra(EXTRA_SELECTED_MASTER_KEY_IDS); + ArrayList<String> missingUserIds = intent + .getStringArrayListExtra(EXTRA_MISSING_USER_IDS); + ArrayList<String> dublicateUserIds = intent + .getStringArrayListExtra(EXTRA_DUBLICATE_USER_IDS); + + String text = new String(); + text += "<b>" + getString(R.string.api_select_pub_keys_text) + "</b>"; + text += "<br/><br/>"; + if (missingUserIds != null && missingUserIds.size() > 0) { + text += getString(R.string.api_select_pub_keys_missing_text); + text += "<br/>"; + text += "<ul>"; + for (String userId : missingUserIds) { + text += "<li>" + userId + "</li>"; + } + text += "</ul>"; + text += "<br/>"; + } + if (dublicateUserIds != null && dublicateUserIds.size() > 0) { + text += getString(R.string.api_select_pub_keys_dublicates_text); + text += "<br/>"; + text += "<ul>"; + for (String userId : dublicateUserIds) { + text += "<li>" + userId + "</li>"; + } + text += "</ul>"; + } + + // Inflate a "Done"/"Cancel" custom action bar view + ActionBarHelper.setDoneCancelView(getSupportActionBar(), R.string.btn_okay, + new View.OnClickListener() { + @Override + public void onClick(View v) { + // ok + + Message msg = Message.obtain(); + msg.arg1 = OpenPgpService.SelectPubKeysActivityCallback.OKAY; + Bundle data = new Bundle(); + data.putLongArray( + OpenPgpService.SelectPubKeysActivityCallback.PUB_KEY_IDS, + mSelectFragment.getSelectedMasterKeyIds()); + msg.setData(data); + try { + mMessenger.send(msg); + } catch (RemoteException e) { + Log.e(Constants.TAG, "CryptoServiceActivity", e); + } + + finishHandled = true; + finish(); + } + }, R.string.btn_doNotSave, new View.OnClickListener() { + @Override + public void onClick(View v) { + // cancel + + Message msg = Message.obtain(); + msg.arg1 = OpenPgpService.SelectPubKeysActivityCallback.CANCEL; + ; + try { + mMessenger.send(msg); + } catch (RemoteException e) { + Log.e(Constants.TAG, "CryptoServiceActivity", e); + } + + finishHandled = true; + finish(); + } + }); + + setContentView(R.layout.api_app_select_pub_keys_activity); + + // set text on view + HtmlTextView textView = (HtmlTextView) findViewById(R.id.api_select_pub_keys_text); + textView.setHtmlFromString(text); + + /* Load select pub keys fragment */ + // Check that the activity is using the layout version with + // the fragment_container FrameLayout + if (findViewById(R.id.api_select_pub_keys_fragment_container) != null) { + + // However, if we're being restored from a previous state, + // then we don't need to do anything and should return or else + // we could end up with overlapping fragments. + if (savedInstanceState != null) { + return; + } + + // Create an instance of the fragment + mSelectFragment = SelectPublicKeyFragment.newInstance(selectedMasterKeyIds); + + // Add the fragment to the 'fragment_container' FrameLayout + getSupportFragmentManager().beginTransaction() + .add(R.id.api_select_pub_keys_fragment_container, mSelectFragment).commit(); + } + } 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) { + Message msg = Message.obtain(); + msg.arg1 = OpenPgpService.PassphraseActivityCallback.OKAY; + try { + mMessenger.send(msg); + } catch (RemoteException e) { + Log.e(Constants.TAG, "CryptoServiceActivity", e); + } + } else { + Message msg = Message.obtain(); + msg.arg1 = OpenPgpService.PassphraseActivityCallback.CANCEL; + try { + mMessenger.send(msg); + } catch (RemoteException e) { + Log.e(Constants.TAG, "CryptoServiceActivity", e); + } + } + + finishHandled = true; + 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/openpgp_api/RegisteredAppsAdapter.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/openpgp_api/RegisteredAppsAdapter.java new file mode 100644 index 000000000..bddf3d4a8 --- /dev/null +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/openpgp_api/RegisteredAppsAdapter.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.openpgp_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/openpgp_api/RegisteredAppsListActivity.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/openpgp_api/RegisteredAppsListActivity.java new file mode 100644 index 000000000..d8d7c3d5c --- /dev/null +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/openpgp_api/RegisteredAppsListActivity.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.openpgp_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/openpgp_api/RegisteredAppsListFragment.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/openpgp_api/RegisteredAppsListFragment.java new file mode 100644 index 000000000..391b86d72 --- /dev/null +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/openpgp_api/RegisteredAppsListFragment.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.openpgp_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 |