aboutsummaryrefslogtreecommitdiffstats
path: root/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java
diff options
context:
space:
mode:
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java')
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java335
1 files changed, 288 insertions, 47 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java
index 5f9c98ac5..f9b0227a2 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java
@@ -18,10 +18,11 @@
package org.sufficientlysecure.keychain.service;
-import android.app.IntentService;
+import android.app.Service;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
@@ -68,7 +69,13 @@ import org.sufficientlysecure.keychain.util.ParcelableFileCache;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import de.measite.minidns.Client;
@@ -80,10 +87,9 @@ import de.measite.minidns.record.TXT;
/**
* This Service contains all important long lasting operations for OpenKeychain. It receives Intents with
- * data from the activities or other apps, queues these intents, executes them, and stops itself
- * after doing them.
+ * data from the activities or other apps, executes them, and stops itself after doing them.
*/
-public class KeychainIntentService extends IntentService implements Progressable {
+public class KeychainIntentService extends Service implements Progressable {
/* extras that can be given by intent */
public static final String EXTRA_MESSENGER = "messenger";
@@ -168,18 +174,29 @@ public class KeychainIntentService extends IntentService implements Progressable
// this attribute can possibly merged with the one above? not sure...
private AtomicBoolean mActionCanceled = new AtomicBoolean(false);
- public KeychainIntentService() {
- super("KeychainIntentService");
+
+ private KeyImportAccumulator mKeyImportAccumulator;
+
+ private KeychainIntentService mKeychainService;
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
}
/**
- * The IntentService calls this method from the default worker thread with the intent that
- * started the service. When this method returns, IntentService stops the service, as
- * appropriate.
+ * This is run on the main thread, we need to spawn a runnable which runs on another thread for the actual operation
*/
@Override
- protected void onHandleIntent(Intent intent) {
+ public int onStartCommand(final Intent intent, int flags, int startId) {
+ mKeychainService = this;
+
+ if (ACTION_CANCEL.equals(intent.getAction())) {
+ mActionCanceled.set(true);
+ return START_NOT_STICKY;
+ }
+ Runnable actionRunnable = new Runnable(){@Override public void run() {
// We have not been cancelled! (yet)
mActionCanceled.set(false);
@@ -207,7 +224,7 @@ public class KeychainIntentService extends IntentService implements Progressable
Log.logDebugBundle(data, "EXTRA_DATA");
- ProviderHelper providerHelper = new ProviderHelper(this);
+ ProviderHelper providerHelper = new ProviderHelper(mKeychainService);
String action = intent.getAction();
@@ -221,7 +238,8 @@ public class KeychainIntentService extends IntentService implements Progressable
String keyServerUri = data.getString(UPLOAD_KEY_SERVER);
// Operation
- CertifyOperation op = new CertifyOperation(this, providerHelper, this, mActionCanceled);
+ CertifyOperation op = new CertifyOperation(mKeychainService, providerHelper, mKeychainService,
+ mActionCanceled);
CertifyResult result = op.certify(parcel, cryptoInput, keyServerUri);
// Result
@@ -234,9 +252,9 @@ public class KeychainIntentService extends IntentService implements Progressable
// Operation
ConsolidateResult result;
if (data.containsKey(CONSOLIDATE_RECOVERY) && data.getBoolean(CONSOLIDATE_RECOVERY)) {
- result = new ProviderHelper(this).consolidateDatabaseStep2(this);
+ result = providerHelper.consolidateDatabaseStep2(mKeychainService);
} else {
- result = new ProviderHelper(this).consolidateDatabaseStep1(this);
+ result = providerHelper.consolidateDatabaseStep1(mKeychainService);
}
// Result
@@ -254,7 +272,7 @@ public class KeychainIntentService extends IntentService implements Progressable
input.setDecryptMetadataOnly(true);
/* Operation */
- PgpDecryptVerify op = new PgpDecryptVerify(this, new ProviderHelper(this), this);
+ PgpDecryptVerify op = new PgpDecryptVerify(mKeychainService, providerHelper, mKeychainService);
DecryptVerifyResult decryptVerifyResult = op.execute(input, cryptoInput);
/* Result */
@@ -271,7 +289,8 @@ public class KeychainIntentService extends IntentService implements Progressable
Prover prover = Prover.findProverFor(proof);
if (prover == null) {
- sendProofError(getString(R.string.keybase_no_prover_found) + ": " + proof.getPrettyName());
+ sendProofError(getString(R.string.keybase_no_prover_found) + ": " + proof
+ .getPrettyName());
return;
}
@@ -308,14 +327,15 @@ public class KeychainIntentService extends IntentService implements Progressable
byte[] messageBytes = prover.getPgpMessage().getBytes();
if (prover.rawMessageCheckRequired()) {
- InputStream messageByteStream = PGPUtil.getDecoderStream(new ByteArrayInputStream(messageBytes));
+ InputStream messageByteStream = PGPUtil.getDecoderStream(new ByteArrayInputStream
+ (messageBytes));
if (!prover.checkRawMessageBytes(messageByteStream)) {
sendProofError(prover.getLog(), null);
return;
}
}
- PgpDecryptVerify op = new PgpDecryptVerify(this, new ProviderHelper(this), this);
+ PgpDecryptVerify op = new PgpDecryptVerify(mKeychainService, providerHelper, mKeychainService);
PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(messageBytes)
.setSignedLiteralData(true)
@@ -344,7 +364,8 @@ public class KeychainIntentService extends IntentService implements Progressable
// these help the handler construct a useful human-readable message
resultData.putString(ServiceProgressHandler.KEYBASE_PROOF_URL, prover.getProofUrl());
resultData.putString(ServiceProgressHandler.KEYBASE_PRESENCE_URL, prover.getPresenceUrl());
- resultData.putString(ServiceProgressHandler.KEYBASE_PRESENCE_LABEL, prover.getPresenceLabel());
+ resultData.putString(ServiceProgressHandler.KEYBASE_PRESENCE_LABEL, prover
+ .getPresenceLabel());
sendMessageToHandler(MessageStatus.OKAY, resultData);
} catch (Exception e) {
sendErrorToHandler(e);
@@ -363,7 +384,7 @@ public class KeychainIntentService extends IntentService implements Progressable
input.setDecryptMetadataOnly(false);
/* Operation */
- PgpDecryptVerify op = new PgpDecryptVerify(this, new ProviderHelper(this), this);
+ PgpDecryptVerify op = new PgpDecryptVerify(mKeychainService, providerHelper, mKeychainService);
DecryptVerifyResult decryptVerifyResult = op.execute(input, cryptoInput);
/* Output */
@@ -378,7 +399,7 @@ public class KeychainIntentService extends IntentService implements Progressable
boolean isSecret = data.getBoolean(DELETE_IS_SECRET);
// Operation
- DeleteOperation op = new DeleteOperation(this, new ProviderHelper(this), this);
+ DeleteOperation op = new DeleteOperation(mKeychainService, providerHelper, mKeychainService);
DeleteResult result = op.execute(masterKeyIds, isSecret);
// Result
@@ -393,7 +414,8 @@ public class KeychainIntentService extends IntentService implements Progressable
CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
// Operation
- EditKeyOperation op = new EditKeyOperation(this, providerHelper, this, mActionCanceled);
+ EditKeyOperation op = new EditKeyOperation(mKeychainService, providerHelper,
+ mKeychainService, mActionCanceled);
OperationResult result = op.execute(saveParcel, cryptoInput);
// Result
@@ -410,7 +432,8 @@ public class KeychainIntentService extends IntentService implements Progressable
// Operation
PromoteKeyOperation op = new PromoteKeyOperation(
- this, providerHelper, this, mActionCanceled);
+ mKeychainService, providerHelper, mKeychainService,
+ mActionCanceled);
PromoteKeyResult result = op.execute(keyRingId, cardAid, subKeyIds);
// Result
@@ -429,7 +452,8 @@ public class KeychainIntentService extends IntentService implements Progressable
long[] masterKeyIds = exportAll ? null : data.getLongArray(EXPORT_KEY_RING_MASTER_KEY_ID);
// Operation
- ImportExportOperation importExportOperation = new ImportExportOperation(this, new ProviderHelper(this), this);
+ ImportExportOperation importExportOperation = new ImportExportOperation(
+ mKeychainService, providerHelper, mKeychainService);
ExportResult result;
if (outputFile != null) {
result = importExportOperation.exportToFile(masterKeyIds, exportSecret, outputFile);
@@ -446,23 +470,27 @@ public class KeychainIntentService extends IntentService implements Progressable
// Input
String keyServer = data.getString(IMPORT_KEY_SERVER);
- ArrayList<ParcelableKeyRing> list = data.getParcelableArrayList(IMPORT_KEY_LIST);
- ParcelableFileCache<ParcelableKeyRing> cache =
- new ParcelableFileCache<>(this, "key_import.pcl");
+ ArrayList<ParcelableKeyRing> keyList = data.getParcelableArrayList(IMPORT_KEY_LIST);
- // Operation
- ImportExportOperation importExportOperation = new ImportExportOperation(
- this, providerHelper, this, mActionCanceled);
- // Either list or cache must be null, no guarantees otherwise.
- ImportKeyResult result = list != null
- ? importExportOperation.importKeyRings(list, keyServer)
- : importExportOperation.importKeyRings(cache, keyServer);
-
- // Result
- sendMessageToHandler(MessageStatus.OKAY, result);
+ // either keyList or cache must be null, no guarantees otherwise
+ if (keyList == null) {// import from file, do serially
+ serialKeyImport(null, keyServer, providerHelper);
+ } else {
+ // if there is more than one key with the same fingerprint, we do a serial import to prevent
+ // https://github.com/open-keychain/open-keychain/issues/1221
+ HashSet<String> keyFingerprintSet = new HashSet<>();
+ for (int i = 0; i < keyList.size(); i++) {
+ keyFingerprintSet.add(keyList.get(i).mExpectedFingerprint);
+ }
+ if (keyFingerprintSet.size() == keyList.size()) {
+ // all keys have unique fingerprints
+ multiThreadedKeyImport(keyList.iterator(), keyList.size(), keyServer);
+ } else {
+ serialKeyImport(keyList, keyServer, providerHelper);
+ }
+ }
break;
-
}
case ACTION_SIGN_ENCRYPT: {
@@ -472,7 +500,7 @@ public class KeychainIntentService extends IntentService implements Progressable
// Operation
SignEncryptOperation op = new SignEncryptOperation(
- this, new ProviderHelper(this), this, mActionCanceled);
+ mKeychainService, providerHelper, mKeychainService, mActionCanceled);
SignEncryptResult result = op.execute(inputParcel, cryptoInput);
// Result
@@ -491,7 +519,8 @@ public class KeychainIntentService extends IntentService implements Progressable
HkpKeyserver server = new HkpKeyserver(keyServer);
CanonicalizedPublicKeyRing keyring = providerHelper.getCanonicalizedPublicKeyRing(dataUri);
- ImportExportOperation importExportOperation = new ImportExportOperation(this, new ProviderHelper(this), this);
+ ImportExportOperation importExportOperation = new ImportExportOperation(mKeychainService,
+ providerHelper, mKeychainService);
try {
importExportOperation.uploadKeyRingToServer(server, keyring);
@@ -506,6 +535,13 @@ public class KeychainIntentService extends IntentService implements Progressable
break;
}
}
+ if (!intent.getAction().equals(ACTION_IMPORT_KEYRING)) {
+ // import keyring handles stopping service on its own
+ stopSelf();
+ } }};
+ Thread actionThread = new Thread(actionRunnable);
+ actionThread.start();
+ return START_NOT_STICKY;
}
private void sendProofError(List<String> log, String label) {
@@ -529,7 +565,7 @@ public class KeychainIntentService extends IntentService implements Progressable
// contextualize the exception, if necessary
String message;
if (e instanceof PgpGeneralMsgIdException) {
- e = ((PgpGeneralMsgIdException) e).getContextualized(this);
+ e = ((PgpGeneralMsgIdException) e).getContextualized(mKeychainService);
message = e.getMessage();
} else {
message = e.getMessage();
@@ -579,6 +615,7 @@ public class KeychainIntentService extends IntentService implements Progressable
/**
* Set progress of ProgressDialog by sending message to handler on UI thread
*/
+ @Override
public void setProgress(String message, int progress, int max) {
Log.d(Constants.TAG, "Send message by setProgress with progress=" + progress + ", max="
+ max);
@@ -593,10 +630,12 @@ public class KeychainIntentService extends IntentService implements Progressable
sendMessageToHandler(MessageStatus.UPDATE_PROGRESS, null, data);
}
+ @Override
public void setProgress(int resourceId, int progress, int max) {
setProgress(getString(resourceId), progress, max);
}
+ @Override
public void setProgress(int progress, int max) {
setProgress(null, progress, max);
}
@@ -606,12 +645,214 @@ public class KeychainIntentService extends IntentService implements Progressable
sendMessageToHandler(MessageStatus.PREVENT_CANCEL);
}
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- if (ACTION_CANCEL.equals(intent.getAction())) {
- mActionCanceled.set(true);
- return START_NOT_STICKY;
+ public void serialKeyImport(ArrayList<ParcelableKeyRing> keyList, final String keyServer,
+ ProviderHelper providerHelper) {
+ Log.d(Constants.TAG, "serial key import starting");
+ ParcelableFileCache<ParcelableKeyRing> cache =
+ new ParcelableFileCache<>(mKeychainService, "key_import.pcl");
+
+ // Operation
+ ImportExportOperation importExportOperation = new ImportExportOperation(
+ mKeychainService, providerHelper, mKeychainService,
+ mActionCanceled);
+ // Either list or cache must be null, no guarantees otherwise.
+ ImportKeyResult result = keyList != null
+ ? importExportOperation.importKeyRings(keyList, keyServer)
+ : importExportOperation.importKeyRings(cache, keyServer);
+
+ ContactSyncAdapterService.requestSync();
+ // Result
+ sendMessageToHandler(MessageStatus.OKAY, result);
+
+ stopSelf();
+ }
+
+ public void multiThreadedKeyImport(Iterator<ParcelableKeyRing> keyListIterator, int totKeys, final String
+ keyServer) {
+ Log.d(Constants.TAG, "Multi-threaded key import starting");
+ if (keyListIterator != null) {
+ mKeyImportAccumulator = new KeyImportAccumulator(totKeys, mKeychainService);
+ setProgress(0, totKeys);
+
+ final int maxThreads = 200;
+ ExecutorService importExecutor = new ThreadPoolExecutor(0, maxThreads,
+ 30L, TimeUnit.SECONDS,
+ new SynchronousQueue<Runnable>());
+
+ while (keyListIterator.hasNext()) {
+
+ final ParcelableKeyRing pkRing = keyListIterator.next();
+
+ Runnable importOperationRunnable = new Runnable() {
+
+ @Override
+ public void run() {
+ ImportKeyResult result = null;
+ try {
+ ImportExportOperation importExportOperation = new ImportExportOperation(
+ mKeychainService,
+ new ProviderHelper(mKeychainService),
+ mKeyImportAccumulator.getImportProgressable(),
+ mActionCanceled);
+
+ ArrayList<ParcelableKeyRing> list = new ArrayList<>();
+ list.add(pkRing);
+
+ result = importExportOperation.importKeyRings(list,
+ keyServer);
+ } finally {
+ // in the off-chance that importKeyRings does something to crash the
+ // thread before it can call singleKeyRingImportCompleted, our imported
+ // key count will go wrong. This will cause the service to never die,
+ // and the progress dialog to stay displayed. The finally block was
+ // originally meant to ensure singleKeyRingImportCompleted was called,
+ // and checks for null were to be introduced, but in such a scenario,
+ // knowing an uncaught error exists in importKeyRings is more important.
+
+ // if a null gets passed, something wrong is happening. We want a crash.
+
+ mKeyImportAccumulator.singleKeyRingImportCompleted(result);
+ }
+ }
+ };
+
+ importExecutor.execute(importOperationRunnable);
+ }
+ }
+ }
+
+ /**
+ * Used to accumulate the results of individual key imports
+ */
+ private class KeyImportAccumulator {
+ private OperationResult.OperationLog mImportLog = new OperationResult.OperationLog();
+ private int mTotalKeys;
+ private int mImportedKeys = 0;
+ private Progressable mInternalProgressable;
+ ArrayList<Long> mImportedMasterKeyIds = new ArrayList<Long>();
+ private int mBadKeys = 0;
+ private int mNewKeys = 0;
+ private int mUpdatedKeys = 0;
+ private int mSecret = 0;
+ private int mResultType = 0;
+
+ /**
+ * meant to be used with a service due to stopSelf() in singleKeyRingImportCompleted. Remove this if
+ * generalising.
+ *
+ * @param totalKeys total number of keys to be imported
+ * @param externalProgressable the external progressable to be updated every time a key is imported
+ */
+ public KeyImportAccumulator(int totalKeys, Progressable externalProgressable) {
+ mTotalKeys = totalKeys;
+ // ignore updates from ImportExportOperation for now
+ mInternalProgressable = new Progressable() {
+ @Override
+ public void setProgress(String message, int current, int total) {
+
+ }
+
+ @Override
+ public void setProgress(int resourceId, int current, int total) {
+
+ }
+
+ @Override
+ public void setProgress(int current, int total) {
+
+ }
+
+ @Override
+ public void setPreventCancel() {
+
+ }
+ };
+ }
+
+ private synchronized void singleKeyRingImportCompleted(ImportKeyResult result) {
+ // increase imported key count and accumulate log and bad, new etc. key counts from result
+ mKeyImportAccumulator.accumulateKeyImport(result);
+
+ setProgress(mKeyImportAccumulator.getImportedKeys(), mKeyImportAccumulator.getTotalKeys());
+
+ if (mKeyImportAccumulator.isImportFinished()) {
+ ContactSyncAdapterService.requestSync();
+
+ sendMessageToHandler(ServiceProgressHandler.MessageStatus.OKAY,
+ mKeyImportAccumulator.getConsolidatedImportKeyResult());
+
+ stopSelf();//we're done here
+ }
+ }
+
+ public Progressable getImportProgressable() {
+ return mInternalProgressable;
+ }
+
+ public int getTotalKeys() {
+ return mTotalKeys;
+ }
+
+ public int getImportedKeys() {
+ return mImportedKeys;
+ }
+
+ public synchronized void accumulateKeyImport(ImportKeyResult result) {
+ mImportedKeys++;
+ mImportLog.addAll(result.getLog().toList());//accumulates log
+ mBadKeys += result.mBadKeys;
+ mNewKeys += result.mNewKeys;
+ mUpdatedKeys += result.mUpdatedKeys;
+ mSecret += result.mSecret;
+
+ long[] masterKeyIds = result.getImportedMasterKeyIds();
+ for (long masterKeyId : masterKeyIds) {
+ mImportedMasterKeyIds.add(masterKeyId);
+ }
+
+ // if any key import has been cancelled, set result type to cancelled
+ // resultType is added to in getConsolidatedKayImport to account for remaining factors
+ mResultType |= result.getResult() & ImportKeyResult.RESULT_CANCELLED;
+ }
+
+ /**
+ * returns accumulated result of all imports so far
+ */
+ public ImportKeyResult getConsolidatedImportKeyResult() {
+
+ // adding required information to mResultType
+ // special case,no keys requested for import
+ if (mBadKeys == 0 && mNewKeys == 0 && mUpdatedKeys == 0) {
+ mResultType = ImportKeyResult.RESULT_FAIL_NOTHING;
+ } else {
+ if (mNewKeys > 0) {
+ mResultType |= ImportKeyResult.RESULT_OK_NEWKEYS;
+ }
+ if (mUpdatedKeys > 0) {
+ mResultType |= ImportKeyResult.RESULT_OK_UPDATED;
+ }
+ if (mBadKeys > 0) {
+ mResultType |= ImportKeyResult.RESULT_WITH_ERRORS;
+ if (mNewKeys == 0 && mUpdatedKeys == 0) {
+ mResultType |= ImportKeyResult.RESULT_ERROR;
+ }
+ }
+ if (mImportLog.containsWarnings()) {
+ mResultType |= ImportKeyResult.RESULT_WARNINGS;
+ }
+ }
+
+ long masterKeyIds[] = new long[mImportedMasterKeyIds.size()];
+ for (int i = 0; i < masterKeyIds.length; i++) {
+ masterKeyIds[i] = mImportedMasterKeyIds.get(i);
+ }
+
+ return new ImportKeyResult(mResultType, mImportLog, mNewKeys, mUpdatedKeys, mBadKeys,
+ mSecret, masterKeyIds);
+ }
+
+ public boolean isImportFinished() {
+ return mTotalKeys == mImportedKeys;
}
- return super.onStartCommand(intent, flags, startId);
}
}