diff options
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service')
19 files changed, 1645 insertions, 1311 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java index a7571a7ac..3cdbca633 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java @@ -22,14 +22,13 @@ import android.os.Parcel; import android.os.Parcelable; import java.io.Serializable; -import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.Date; import java.util.Map; import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; -import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.util.ParcelableProxy; /** @@ -44,6 +43,8 @@ public class CertifyActionsParcel implements Parcelable { public ArrayList<CertifyAction> mCertifyActions = new ArrayList<>(); + public String keyServerUri; + public CertifyActionsParcel(long masterKeyId) { mMasterKeyId = masterKeyId; mLevel = CertifyLevel.DEFAULT; @@ -53,6 +54,7 @@ public class CertifyActionsParcel implements Parcelable { mMasterKeyId = source.readLong(); // just like parcelables, this is meant for ad-hoc IPC only and is NOT portable! mLevel = CertifyLevel.values()[source.readInt()]; + keyServerUri = source.readString(); mCertifyActions = (ArrayList<CertifyAction>) source.readSerializable(); } @@ -65,6 +67,7 @@ public class CertifyActionsParcel implements Parcelable { public void writeToParcel(Parcel destination, int flags) { destination.writeLong(mMasterKeyId); destination.writeInt(mLevel.ordinal()); + destination.writeString(keyServerUri); destination.writeSerializable(mCertifyActions); } @@ -86,8 +89,7 @@ public class CertifyActionsParcel implements Parcelable { final public ArrayList<String> mUserIds; final public ArrayList<WrappedUserAttribute> mUserAttributes; - public CertifyAction(long masterKeyId, List<String> userIds, - List<WrappedUserAttribute> attributes) { + public CertifyAction(long masterKeyId, List<String> userIds, List<WrappedUserAttribute> attributes) { mMasterKeyId = masterKeyId; mUserIds = userIds == null ? null : new ArrayList<>(userIds); mUserAttributes = attributes == null ? null : new ArrayList<>(attributes); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CloudImportService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CloudImportService.java deleted file mode 100644 index 249586f6d..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CloudImportService.java +++ /dev/null @@ -1,384 +0,0 @@ -/* - * Copyright (C) 2012-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.service; - -import android.app.Service; -import android.content.Intent; -import android.os.Bundle; -import android.os.IBinder; -import android.os.Message; -import android.os.Messenger; -import android.os.RemoteException; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; -import org.sufficientlysecure.keychain.operations.ImportExportOperation; -import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; -import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.pgp.Progressable; -import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.util.ParcelableFileCache; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Iterator; -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; - -/** - * When this service is started it will initiate a multi-threaded key import and when done it will - * shut itself down. - */ -public class CloudImportService extends Service implements Progressable { - - // required as extras from intent - public static final String EXTRA_MESSENGER = "messenger"; - public static final String EXTRA_DATA = "data"; - - // required by data bundle - public static final String IMPORT_KEY_LIST = "import_key_list"; - public static final String IMPORT_KEY_SERVER = "import_key_server"; - - // indicates a request to cancel the import - public static final String ACTION_CANCEL = Constants.INTENT_PREFIX + "CANCEL"; - - // tells the spawned threads whether the user has requested a cancel - private static AtomicBoolean mActionCancelled = new AtomicBoolean(false); - - @Override - public IBinder onBind(Intent intent) { - return null; - } - - /** - * 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 mImportProgressable; - 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; - - public KeyImportAccumulator(int totalKeys) { - mTotalKeys = totalKeys; - // ignore updates from ImportExportOperation for now - mImportProgressable = 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() { - - } - }; - } - - public Progressable getImportProgressable() { - return mImportProgressable; - } - - 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; - } - } - - private KeyImportAccumulator mKeyImportAccumulator; - - Messenger mMessenger; - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - - if (ACTION_CANCEL.equals(intent.getAction())) { - mActionCancelled.set(true); - return Service.START_NOT_STICKY; - } - - mActionCancelled.set(false);//we haven't been cancelled, yet - - Bundle extras = intent.getExtras(); - - mMessenger = (Messenger) extras.get(EXTRA_MESSENGER); - - Bundle data = extras.getBundle(EXTRA_DATA); - - final String keyServer = data.getString(IMPORT_KEY_SERVER); - // keyList being null (in case key list to be reaad from cache) is checked by importKeys - final ArrayList<ParcelableKeyRing> keyList = data.getParcelableArrayList(IMPORT_KEY_LIST); - - // Adding keys to the ThreadPoolExecutor takes time, we don't want to block the main thread - Thread baseImportThread = new Thread(new Runnable() { - - @Override - public void run() { - importKeys(keyList, keyServer); - } - }); - baseImportThread.start(); - return Service.START_NOT_STICKY; - } - - public void importKeys(ArrayList<ParcelableKeyRing> keyList, final String keyServer) { - ParcelableFileCache<ParcelableKeyRing> cache = - new ParcelableFileCache<>(this, "key_import.pcl"); - int totKeys = 0; - Iterator<ParcelableKeyRing> keyListIterator = null; - // either keyList or cache must be null, no guarantees otherwise - if (keyList == null) {//export from cache, copied from ImportExportOperation.importKeyRings - - try { - ParcelableFileCache.IteratorWithSize<ParcelableKeyRing> it = cache.readCache(); - keyListIterator = it; - totKeys = it.getSize(); - } catch (IOException e) { - - // Special treatment here, we need a lot - OperationResult.OperationLog log = new OperationResult.OperationLog(); - log.add(OperationResult.LogType.MSG_IMPORT, 0, 0); - log.add(OperationResult.LogType.MSG_IMPORT_ERROR_IO, 0, 0); - - keyImportFailed(new ImportKeyResult(ImportKeyResult.RESULT_ERROR, log)); - } - } else { - keyListIterator = keyList.iterator(); - totKeys = keyList.size(); - } - - - if (keyListIterator != null) { - mKeyImportAccumulator = new KeyImportAccumulator(totKeys); - 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( - CloudImportService.this, - new ProviderHelper(CloudImportService.this), - mKeyImportAccumulator.getImportProgressable(), - mActionCancelled); - - 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. - - singleKeyRingImportCompleted(result); - } - } - }; - - importExecutor.execute(importOperationRunnable); - } - } - } - - 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 - } - } - - private void keyImportFailed(ImportKeyResult result) { - sendMessageToHandler(ServiceProgressHandler.MessageStatus.OKAY, result); - } - - private void sendMessageToHandler(ServiceProgressHandler.MessageStatus status, Integer arg2, Bundle data) { - - Message msg = Message.obtain(); - assert msg != null; - msg.arg1 = status.ordinal(); - if (arg2 != null) { - msg.arg2 = arg2; - } - if (data != null) { - msg.setData(data); - } - - try { - mMessenger.send(msg); - } catch (RemoteException e) { - Log.w(Constants.TAG, "Exception sending message, Is handler present?", e); - } catch (NullPointerException e) { - Log.w(Constants.TAG, "Messenger is null!", e); - } - } - - private void sendMessageToHandler(ServiceProgressHandler.MessageStatus status, OperationResult data) { - Bundle bundle = new Bundle(); - bundle.putParcelable(OperationResult.EXTRA_RESULT, data); - sendMessageToHandler(status, null, bundle); - } - - private void sendMessageToHandler(ServiceProgressHandler.MessageStatus status, Bundle data) { - sendMessageToHandler(status, null, data); - } - - private void sendMessageToHandler(ServiceProgressHandler.MessageStatus status) { - sendMessageToHandler(status, null, null); - } - - /** - * Set progress of ProgressDialog by sending message to handler on UI thread - */ - @Override - public synchronized void setProgress(String message, int progress, int max) { - Log.d(Constants.TAG, "Send message by setProgress with progress=" + progress + ", max=" - + max); - - Bundle data = new Bundle(); - if (message != null) { - data.putString(ServiceProgressHandler.DATA_MESSAGE, message); - } - data.putInt(ServiceProgressHandler.DATA_PROGRESS, progress); - data.putInt(ServiceProgressHandler.DATA_PROGRESS_MAX, max); - - sendMessageToHandler(ServiceProgressHandler.MessageStatus.UPDATE_PROGRESS, null, data); - } - - @Override - public synchronized void setProgress(int resourceId, int progress, int max) { - setProgress(getString(resourceId), progress, max); - } - - @Override - public synchronized void setProgress(int progress, int max) { - setProgress(null, progress, max); - } - - @Override - public synchronized void setPreventCancel() { - sendMessageToHandler(ServiceProgressHandler.MessageStatus.PREVENT_CANCEL); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ConsolidateInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ConsolidateInputParcel.java new file mode 100644 index 000000000..15d109814 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ConsolidateInputParcel.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.com> + * + * 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.service; + +import android.os.Parcel; +import android.os.Parcelable; + +public class ConsolidateInputParcel implements Parcelable { + + public boolean mConsolidateRecovery; + + public ConsolidateInputParcel(boolean consolidateRecovery) { + mConsolidateRecovery = consolidateRecovery; + } + + protected ConsolidateInputParcel(Parcel in) { + mConsolidateRecovery = in.readByte() != 0x00; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeByte((byte) (mConsolidateRecovery ? 0x01 : 0x00)); + } + + public static final Parcelable.Creator<ConsolidateInputParcel> CREATOR = new Parcelable.Creator<ConsolidateInputParcel>() { + @Override + public ConsolidateInputParcel createFromParcel(Parcel in) { + return new ConsolidateInputParcel(in); + } + + @Override + public ConsolidateInputParcel[] newArray(int size) { + return new ConsolidateInputParcel[size]; + } + }; +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java index 7688b9252..b36d23775 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java @@ -45,7 +45,7 @@ public class ContactSyncAdapterService extends Service { @Override public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, final SyncResult syncResult) { - Log.d(Constants.TAG, "Performing a sync!"); + Log.d(Constants.TAG, "Performing a contact sync!"); // TODO: Import is currently disabled for 2.8, until we implement proper origin management // importDone.set(false); // KeychainApplication.setupAccountAsNeeded(ContactSyncAdapterService.this); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DeleteKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DeleteKeyringParcel.java new file mode 100644 index 000000000..b412a6e2b --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DeleteKeyringParcel.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.com> + * + * 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.service; + +import android.os.Parcel; +import android.os.Parcelable; + +public class DeleteKeyringParcel implements Parcelable { + + public long[] mMasterKeyIds; + public boolean mIsSecret; + + public DeleteKeyringParcel(long[] masterKeyIds, boolean isSecret) { + mMasterKeyIds = masterKeyIds; + mIsSecret = isSecret; + } + + protected DeleteKeyringParcel(Parcel in) { + mIsSecret = in.readByte() != 0x00; + mMasterKeyIds = in.createLongArray(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeByte((byte) (mIsSecret ? 0x01 : 0x00)); + dest.writeLongArray(mMasterKeyIds); + } + + public static final Parcelable.Creator<DeleteKeyringParcel> CREATOR = new Parcelable.Creator<DeleteKeyringParcel>() { + @Override + public DeleteKeyringParcel createFromParcel(Parcel in) { + return new DeleteKeyringParcel(in); + } + + @Override + public DeleteKeyringParcel[] newArray(int size) { + return new DeleteKeyringParcel[size]; + } + }; +} + diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DummyAccountService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DummyAccountService.java index 41ff6d02b..18bc3cc9d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DummyAccountService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/DummyAccountService.java @@ -83,7 +83,7 @@ public class DummyAccountService extends Service { public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException { response.onResult(new Bundle()); - toaster.toast(R.string.info_no_manual_account_creation); + toaster.toast(R.string.account_no_manual_account_creation); Log.d(Constants.TAG, "DummyAccountService.addAccount"); return null; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ExportKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ExportKeyringParcel.java new file mode 100644 index 000000000..24c002bbd --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ExportKeyringParcel.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.com> + * + * 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.service; + +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; + +public class ExportKeyringParcel implements Parcelable { + public String mKeyserver; + public Uri mCanonicalizedPublicKeyringUri; + public UncachedKeyRing mUncachedKeyRing; + + public boolean mExportSecret; + public long mMasterKeyIds[]; + public String mOutputFile; + public Uri mOutputUri; + public ExportType mExportType; + + public enum ExportType { + UPLOAD_KEYSERVER, + EXPORT_FILE, + EXPORT_URI + } + + public ExportKeyringParcel(String keyserver, Uri keyringUri) { + mExportType = ExportType.UPLOAD_KEYSERVER; + mKeyserver = keyserver; + mCanonicalizedPublicKeyringUri = keyringUri; + } + + public ExportKeyringParcel(String keyserver, UncachedKeyRing uncachedKeyRing) { + mExportType = ExportType.UPLOAD_KEYSERVER; + mKeyserver = keyserver; + mUncachedKeyRing = uncachedKeyRing; + } + + public ExportKeyringParcel(long[] masterKeyIds, boolean exportSecret, String outputFile) { + mExportType = ExportType.EXPORT_FILE; + mMasterKeyIds = masterKeyIds; + mExportSecret = exportSecret; + mOutputFile = outputFile; + } + + @SuppressWarnings("unused") // TODO: is it used? + public ExportKeyringParcel(long[] masterKeyIds, boolean exportSecret, Uri outputUri) { + mExportType = ExportType.EXPORT_URI; + mMasterKeyIds = masterKeyIds; + mExportSecret = exportSecret; + mOutputUri = outputUri; + } + + protected ExportKeyringParcel(Parcel in) { + mKeyserver = in.readString(); + mCanonicalizedPublicKeyringUri = (Uri) in.readValue(Uri.class.getClassLoader()); + mUncachedKeyRing = (UncachedKeyRing) in.readValue(UncachedKeyRing.class.getClassLoader()); + mExportSecret = in.readByte() != 0x00; + mOutputFile = in.readString(); + mOutputUri = (Uri) in.readValue(Uri.class.getClassLoader()); + mExportType = (ExportType) in.readValue(ExportType.class.getClassLoader()); + mMasterKeyIds = in.createLongArray(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mKeyserver); + dest.writeValue(mCanonicalizedPublicKeyringUri); + dest.writeValue(mUncachedKeyRing); + dest.writeByte((byte) (mExportSecret ? 0x01 : 0x00)); + dest.writeString(mOutputFile); + dest.writeValue(mOutputUri); + dest.writeValue(mExportType); + dest.writeLongArray(mMasterKeyIds); + } + + public static final Parcelable.Creator<ExportKeyringParcel> CREATOR = new Parcelable.Creator<ExportKeyringParcel>() { + @Override + public ExportKeyringParcel createFromParcel(Parcel in) { + return new ExportKeyringParcel(in); + } + + @Override + public ExportKeyringParcel[] newArray(int size) { + return new ExportKeyringParcel[size]; + } + }; +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ImportKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ImportKeyringParcel.java new file mode 100644 index 000000000..a41dd71cb --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ImportKeyringParcel.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.com> + * + * 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.service; + +import android.os.Parcel; +import android.os.Parcelable; +import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; + +import java.util.ArrayList; + +public class ImportKeyringParcel implements Parcelable { + // if null, keys are expected to be read from a cache file in ImportExportOperations + public ArrayList<ParcelableKeyRing> mKeyList; + public String mKeyserver; // must be set if keys are to be imported from a keyserver + + public ImportKeyringParcel (ArrayList<ParcelableKeyRing> keyList, String keyserver) { + mKeyList = keyList; + mKeyserver = keyserver; + } + + protected ImportKeyringParcel(Parcel in) { + if (in.readByte() == 0x01) { + mKeyList = new ArrayList<>(); + in.readList(mKeyList, ParcelableKeyRing.class.getClassLoader()); + } else { + mKeyList = null; + } + mKeyserver = in.readString(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + if (mKeyList == null) { + dest.writeByte((byte) (0x00)); + } else { + dest.writeByte((byte) (0x01)); + dest.writeList(mKeyList); + } + dest.writeString(mKeyserver); + } + + public static final Parcelable.Creator<ImportKeyringParcel> CREATOR = new Parcelable.Creator<ImportKeyringParcel>() { + @Override + public ImportKeyringParcel createFromParcel(Parcel in) { + return new ImportKeyringParcel(in); + } + + @Override + public ImportKeyringParcel[] newArray(int size) { + return new ImportKeyringParcel[size]; + } + }; +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeybaseVerificationParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeybaseVerificationParcel.java new file mode 100644 index 000000000..1872191af --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeybaseVerificationParcel.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.com> + * + * 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.service; + +import android.os.Parcel; +import android.os.Parcelable; + +public class KeybaseVerificationParcel implements Parcelable { + + public String mKeybaseProof; + public String mRequiredFingerprint; + + public KeybaseVerificationParcel(String keybaseProof, String requiredFingerprint) { + mKeybaseProof = keybaseProof; + mRequiredFingerprint = requiredFingerprint; + } + + protected KeybaseVerificationParcel(Parcel in) { + mKeybaseProof = in.readString(); + mRequiredFingerprint = in.readString(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mKeybaseProof); + dest.writeString(mRequiredFingerprint); + } + + public static final Parcelable.Creator<KeybaseVerificationParcel> CREATOR = new Parcelable.Creator<KeybaseVerificationParcel>() { + @Override + public KeybaseVerificationParcel createFromParcel(Parcel in) { + return new KeybaseVerificationParcel(in); + } + + @Override + public KeybaseVerificationParcel[] newArray(int size) { + return new KeybaseVerificationParcel[size]; + } + }; +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java deleted file mode 100644 index 63ea6285c..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ /dev/null @@ -1,752 +0,0 @@ -/* - * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> - * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> - * - * 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.service; - -import android.app.IntentService; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.os.Message; -import android.os.Messenger; -import android.os.RemoteException; - -import com.textuality.keybase.lib.Proof; -import com.textuality.keybase.lib.prover.Prover; - -import org.json.JSONObject; -import org.spongycastle.openpgp.PGPUtil; -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.keyimport.HkpKeyserver; -import org.sufficientlysecure.keychain.keyimport.Keyserver; -import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; -import org.sufficientlysecure.keychain.operations.CertifyOperation; -import org.sufficientlysecure.keychain.operations.DeleteOperation; -import org.sufficientlysecure.keychain.operations.EditKeyOperation; -import org.sufficientlysecure.keychain.operations.ImportExportOperation; -import org.sufficientlysecure.keychain.operations.PromoteKeyOperation; -import org.sufficientlysecure.keychain.operations.SignEncryptOperation; -import org.sufficientlysecure.keychain.operations.results.CertifyResult; -import org.sufficientlysecure.keychain.operations.results.ConsolidateResult; -import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; -import org.sufficientlysecure.keychain.operations.results.DeleteResult; -import org.sufficientlysecure.keychain.operations.results.ExportResult; -import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; -import org.sufficientlysecure.keychain.operations.results.CertifyResult; -import org.sufficientlysecure.keychain.util.FileHelper; -import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize; -import org.sufficientlysecure.keychain.util.Preferences; -import org.sufficientlysecure.keychain.keyimport.HkpKeyserver; -import org.sufficientlysecure.keychain.keyimport.Keyserver; -import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; -import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; -import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; -import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult; -import org.sufficientlysecure.keychain.operations.results.SignEncryptResult; -import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; -import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify; -import org.sufficientlysecure.keychain.pgp.Progressable; -import org.sufficientlysecure.keychain.pgp.SignEncryptParcel; -import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; -import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException; -import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; -import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus; -import org.sufficientlysecure.keychain.util.FileHelper; -import org.sufficientlysecure.keychain.util.InputData; -import org.sufficientlysecure.keychain.util.Log; -import org.sufficientlysecure.keychain.util.ParcelableFileCache; -import org.sufficientlysecure.keychain.util.Passphrase; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; - -import de.measite.minidns.Client; -import de.measite.minidns.DNSMessage; -import de.measite.minidns.Question; -import de.measite.minidns.Record; -import de.measite.minidns.record.Data; -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. - */ -public class KeychainIntentService extends IntentService implements Progressable { - - /* extras that can be given by intent */ - public static final String EXTRA_MESSENGER = "messenger"; - public static final String EXTRA_DATA = "data"; - - /* possible actions */ - public static final String ACTION_SIGN_ENCRYPT = Constants.INTENT_PREFIX + "SIGN_ENCRYPT"; - - public static final String ACTION_DECRYPT_VERIFY = Constants.INTENT_PREFIX + "DECRYPT_VERIFY"; - - public static final String ACTION_VERIFY_KEYBASE_PROOF = Constants.INTENT_PREFIX + "VERIFY_KEYBASE_PROOF"; - - public static final String ACTION_DECRYPT_METADATA = Constants.INTENT_PREFIX + "DECRYPT_METADATA"; - - public static final String ACTION_EDIT_KEYRING = Constants.INTENT_PREFIX + "EDIT_KEYRING"; - - public static final String ACTION_PROMOTE_KEYRING = Constants.INTENT_PREFIX + "PROMOTE_KEYRING"; - - public static final String ACTION_IMPORT_KEYRING = Constants.INTENT_PREFIX + "IMPORT_KEYRING"; - public static final String ACTION_EXPORT_KEYRING = Constants.INTENT_PREFIX + "EXPORT_KEYRING"; - - public static final String ACTION_UPLOAD_KEYRING = Constants.INTENT_PREFIX + "UPLOAD_KEYRING"; - - public static final String ACTION_CERTIFY_KEYRING = Constants.INTENT_PREFIX + "SIGN_KEYRING"; - - public static final String ACTION_DELETE = Constants.INTENT_PREFIX + "DELETE"; - - public static final String ACTION_CONSOLIDATE = Constants.INTENT_PREFIX + "CONSOLIDATE"; - - public static final String ACTION_CANCEL = Constants.INTENT_PREFIX + "CANCEL"; - - /* keys for data bundle */ - - // encrypt, decrypt, import export - public static final String TARGET = "target"; - public static final String SOURCE = "source"; - - // possible targets: - public static enum IOType { - UNKNOWN, - BYTES, - URI; - - private static final IOType[] values = values(); - - public static IOType fromInt(int n) { - if (n < 0 || n >= values.length) { - return UNKNOWN; - } else { - return values[n]; - } - } - } - - // encrypt - public static final String ENCRYPT_DECRYPT_INPUT_URI = "input_uri"; - public static final String ENCRYPT_DECRYPT_OUTPUT_URI = "output_uri"; - public static final String SIGN_ENCRYPT_PARCEL = "sign_encrypt_parcel"; - - // decrypt/verify - public static final String DECRYPT_CIPHERTEXT_BYTES = "ciphertext_bytes"; - - // keybase proof - public static final String KEYBASE_REQUIRED_FINGERPRINT = "keybase_required_fingerprint"; - public static final String KEYBASE_PROOF = "keybase_proof"; - - // save keyring - public static final String EDIT_KEYRING_PARCEL = "save_parcel"; - public static final String EDIT_KEYRING_PASSPHRASE = "passphrase"; - public static final String EXTRA_CRYPTO_INPUT = "crypto_input"; - - // delete keyring(s) - public static final String DELETE_KEY_LIST = "delete_list"; - public static final String DELETE_IS_SECRET = "delete_is_secret"; - - // import key - public static final String IMPORT_KEY_LIST = "import_key_list"; - public static final String IMPORT_KEY_SERVER = "import_key_server"; - - // export key - public static final String EXPORT_FILENAME = "export_filename"; - public static final String EXPORT_URI = "export_uri"; - public static final String EXPORT_SECRET = "export_secret"; - public static final String EXPORT_ALL = "export_all"; - public static final String EXPORT_KEY_RING_MASTER_KEY_ID = "export_key_ring_id"; - - // upload key - public static final String UPLOAD_KEY_SERVER = "upload_key_server"; - - // certify key - public static final String CERTIFY_PARCEL = "certify_parcel"; - - // promote key - public static final String PROMOTE_MASTER_KEY_ID = "promote_master_key_id"; - public static final String PROMOTE_CARD_AID = "promote_card_aid"; - - // consolidate - public static final String CONSOLIDATE_RECOVERY = "consolidate_recovery"; - - - /* - * possible data keys as result send over messenger - */ - - // decrypt/verify - public static final String RESULT_DECRYPTED_BYTES = "decrypted_data"; - - Messenger mMessenger; - - // this attribute can possibly merged with the one above? not sure... - private AtomicBoolean mActionCanceled = new AtomicBoolean(false); - - public KeychainIntentService() { - super("KeychainIntentService"); - } - - /** - * 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. - */ - @Override - protected void onHandleIntent(Intent intent) { - - // We have not been cancelled! (yet) - mActionCanceled.set(false); - - Bundle extras = intent.getExtras(); - if (extras == null) { - Log.e(Constants.TAG, "Extras bundle is null!"); - return; - } - - if (!(extras.containsKey(EXTRA_MESSENGER) || extras.containsKey(EXTRA_DATA) || (intent - .getAction() == null))) { - Log.e(Constants.TAG, - "Extra bundle must contain a messenger, a data bundle, and an action!"); - return; - } - - Uri dataUri = intent.getData(); - - mMessenger = (Messenger) extras.get(EXTRA_MESSENGER); - Bundle data = extras.getBundle(EXTRA_DATA); - if (data == null) { - Log.e(Constants.TAG, "data extra is null!"); - return; - } - - Log.logDebugBundle(data, "EXTRA_DATA"); - - ProviderHelper providerHelper = new ProviderHelper(this); - - String action = intent.getAction(); - - // executeServiceMethod action from extra bundle - switch (action) { - case ACTION_CERTIFY_KEYRING: { - - // Input - CertifyActionsParcel parcel = data.getParcelable(CERTIFY_PARCEL); - CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT); - String keyServerUri = data.getString(UPLOAD_KEY_SERVER); - - // Operation - CertifyOperation op = new CertifyOperation(this, providerHelper, this, mActionCanceled); - CertifyResult result = op.certify(parcel, cryptoInput, keyServerUri); - - // Result - sendMessageToHandler(MessageStatus.OKAY, result); - - break; - } - case ACTION_CONSOLIDATE: { - - // Operation - ConsolidateResult result; - if (data.containsKey(CONSOLIDATE_RECOVERY) && data.getBoolean(CONSOLIDATE_RECOVERY)) { - result = new ProviderHelper(this).consolidateDatabaseStep2(this); - } else { - result = new ProviderHelper(this).consolidateDatabaseStep1(this); - } - - // Result - sendMessageToHandler(MessageStatus.OKAY, result); - - break; - } - case ACTION_DECRYPT_METADATA: { - - try { - /* Input */ - CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT); - - InputData inputData = createDecryptInputData(data); - - // verifyText and decrypt returning additional resultData values for the - // verification of signatures - PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder( - this, new ProviderHelper(this), this, inputData, null - ); - builder.setAllowSymmetricDecryption(true) - .setDecryptMetadataOnly(true); - - DecryptVerifyResult decryptVerifyResult = builder.build().execute(cryptoInput); - - sendMessageToHandler(MessageStatus.OKAY, decryptVerifyResult); - } catch (Exception e) { - sendErrorToHandler(e); - } - - break; - } - case ACTION_VERIFY_KEYBASE_PROOF: { - - try { - Proof proof = new Proof(new JSONObject(data.getString(KEYBASE_PROOF))); - setProgress(R.string.keybase_message_fetching_data, 0, 100); - - Prover prover = Prover.findProverFor(proof); - - if (prover == null) { - sendProofError(getString(R.string.keybase_no_prover_found) + ": " + proof.getPrettyName()); - return; - } - - if (!prover.fetchProofData()) { - sendProofError(prover.getLog(), getString(R.string.keybase_problem_fetching_evidence)); - return; - } - String requiredFingerprint = data.getString(KEYBASE_REQUIRED_FINGERPRINT); - if (!prover.checkFingerprint(requiredFingerprint)) { - sendProofError(getString(R.string.keybase_key_mismatch)); - return; - } - - String domain = prover.dnsTxtCheckRequired(); - if (domain != null) { - DNSMessage dnsQuery = new Client().query(new Question(domain, Record.TYPE.TXT)); - if (dnsQuery == null) { - sendProofError(prover.getLog(), getString(R.string.keybase_dns_query_failure)); - return; - } - Record[] records = dnsQuery.getAnswers(); - List<List<byte[]>> extents = new ArrayList<List<byte[]>>(); - for (Record r : records) { - Data d = r.getPayload(); - if (d instanceof TXT) { - extents.add(((TXT) d).getExtents()); - } - } - if (!prover.checkDnsTxt(extents)) { - sendProofError(prover.getLog(), null); - return; - } - } - - byte[] messageBytes = prover.getPgpMessage().getBytes(); - if (prover.rawMessageCheckRequired()) { - InputStream messageByteStream = PGPUtil.getDecoderStream(new ByteArrayInputStream(messageBytes)); - if (!prover.checkRawMessageBytes(messageByteStream)) { - sendProofError(prover.getLog(), null); - return; - } - } - - // kind of awkward, but this whole class wants to pull bytes out of “data” - data.putInt(KeychainIntentService.TARGET, IOType.BYTES.ordinal()); - data.putByteArray(KeychainIntentService.DECRYPT_CIPHERTEXT_BYTES, messageBytes); - - InputData inputData = createDecryptInputData(data); - OutputStream outStream = createCryptOutputStream(data); - - PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder( - this, new ProviderHelper(this), this, - inputData, outStream - ); - builder.setSignedLiteralData(true).setRequiredSignerFingerprint(requiredFingerprint); - - DecryptVerifyResult decryptVerifyResult = builder.build().execute( - new CryptoInputParcel()); - outStream.close(); - - if (!decryptVerifyResult.success()) { - OperationLog log = decryptVerifyResult.getLog(); - OperationResult.LogEntryParcel lastEntry = null; - for (OperationResult.LogEntryParcel entry : log) { - lastEntry = entry; - } - sendProofError(getString(lastEntry.mType.getMsgId())); - return; - } - - if (!prover.validate(outStream.toString())) { - sendProofError(getString(R.string.keybase_message_payload_mismatch)); - return; - } - - Bundle resultData = new Bundle(); - resultData.putString(ServiceProgressHandler.DATA_MESSAGE, "OK"); - - // 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()); - sendMessageToHandler(MessageStatus.OKAY, resultData); - } catch (Exception e) { - sendErrorToHandler(e); - } - - break; - } - case ACTION_DECRYPT_VERIFY: { - - try { - /* Input */ - CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT); - - InputData inputData = createDecryptInputData(data); - OutputStream outStream = createCryptOutputStream(data); - - /* Operation */ - Bundle resultData = new Bundle(); - - // verifyText and decrypt returning additional resultData values for the - // verification of signatures - PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder( - this, new ProviderHelper(this), this, - inputData, outStream - ); - builder.setAllowSymmetricDecryption(true); - - DecryptVerifyResult decryptVerifyResult = builder.build().execute(cryptoInput); - - outStream.close(); - - resultData.putParcelable(DecryptVerifyResult.EXTRA_RESULT, decryptVerifyResult); - - /* Output */ - finalizeDecryptOutputStream(data, resultData, outStream); - Log.logDebugBundle(resultData, "resultData"); - - sendMessageToHandler(MessageStatus.OKAY, resultData); - - } catch (IOException | PgpGeneralException e) { - // TODO get rid of this! - sendErrorToHandler(e); - } - - break; - } - case ACTION_DELETE: { - - // Input - long[] masterKeyIds = data.getLongArray(DELETE_KEY_LIST); - boolean isSecret = data.getBoolean(DELETE_IS_SECRET); - - // Operation - DeleteOperation op = new DeleteOperation(this, new ProviderHelper(this), this); - DeleteResult result = op.execute(masterKeyIds, isSecret); - - // Result - sendMessageToHandler(MessageStatus.OKAY, result); - - break; - } - case ACTION_EDIT_KEYRING: { - - // Input - SaveKeyringParcel saveParcel = data.getParcelable(EDIT_KEYRING_PARCEL); - CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT); - - // Operation - EditKeyOperation op = new EditKeyOperation(this, providerHelper, this, mActionCanceled); - OperationResult result = op.execute(saveParcel, cryptoInput); - - // Result - sendMessageToHandler(MessageStatus.OKAY, result); - - break; - } - case ACTION_PROMOTE_KEYRING: { - - // Input - long keyRingId = data.getLong(PROMOTE_MASTER_KEY_ID); - byte[] cardAid = data.getByteArray(PROMOTE_CARD_AID); - - // Operation - PromoteKeyOperation op = new PromoteKeyOperation(this, providerHelper, this, mActionCanceled); - PromoteKeyResult result = op.execute(keyRingId, cardAid); - - // Result - sendMessageToHandler(MessageStatus.OKAY, result); - - break; - } - case ACTION_EXPORT_KEYRING: { - - // Input - boolean exportSecret = data.getBoolean(EXPORT_SECRET, false); - String outputFile = data.getString(EXPORT_FILENAME); - Uri outputUri = data.getParcelable(EXPORT_URI); - - boolean exportAll = data.getBoolean(EXPORT_ALL); - long[] masterKeyIds = exportAll ? null : data.getLongArray(EXPORT_KEY_RING_MASTER_KEY_ID); - - // Operation - ImportExportOperation importExportOperation = new ImportExportOperation(this, new ProviderHelper(this), this); - ExportResult result; - if (outputFile != null) { - result = importExportOperation.exportToFile(masterKeyIds, exportSecret, outputFile); - } else { - result = importExportOperation.exportToUri(masterKeyIds, exportSecret, outputUri); - } - - // Result - sendMessageToHandler(MessageStatus.OKAY, result); - - break; - } - case ACTION_IMPORT_KEYRING: { - - // 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"); - - // 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); - - break; - - } - case ACTION_SIGN_ENCRYPT: { - - // Input - SignEncryptParcel inputParcel = data.getParcelable(SIGN_ENCRYPT_PARCEL); - CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT); - - // Operation - SignEncryptOperation op = new SignEncryptOperation( - this, new ProviderHelper(this), this, mActionCanceled); - SignEncryptResult result = op.execute(inputParcel, cryptoInput); - - // Result - sendMessageToHandler(MessageStatus.OKAY, result); - - break; - } - case ACTION_UPLOAD_KEYRING: { - try { - - /* Input */ - String keyServer = data.getString(UPLOAD_KEY_SERVER); - // and dataUri! - - /* Operation */ - HkpKeyserver server = new HkpKeyserver(keyServer); - - CanonicalizedPublicKeyRing keyring = providerHelper.getCanonicalizedPublicKeyRing(dataUri); - ImportExportOperation importExportOperation = new ImportExportOperation(this, new ProviderHelper(this), this); - - try { - importExportOperation.uploadKeyRingToServer(server, keyring); - } catch (Keyserver.AddKeyException e) { - throw new PgpGeneralException("Unable to export key to selected server"); - } - - sendMessageToHandler(MessageStatus.OKAY); - } catch (Exception e) { - sendErrorToHandler(e); - } - break; - } - } - } - - private void sendProofError(List<String> log, String label) { - String msg = null; - label = (label == null) ? "" : label + ": "; - for (String m : log) { - Log.e(Constants.TAG, label + m); - msg = m; - } - sendProofError(label + msg); - } - - private void sendProofError(String msg) { - Bundle bundle = new Bundle(); - bundle.putString(ServiceProgressHandler.DATA_ERROR, msg); - sendMessageToHandler(MessageStatus.OKAY, bundle); - } - - private void sendErrorToHandler(Exception e) { - // TODO: Implement a better exception handling here - // contextualize the exception, if necessary - String message; - if (e instanceof PgpGeneralMsgIdException) { - e = ((PgpGeneralMsgIdException) e).getContextualized(this); - message = e.getMessage(); - } else { - message = e.getMessage(); - } - Log.d(Constants.TAG, "KeychainIntentService Exception: ", e); - - Bundle data = new Bundle(); - data.putString(ServiceProgressHandler.DATA_ERROR, message); - sendMessageToHandler(MessageStatus.EXCEPTION, null, data); - } - - private void sendMessageToHandler(MessageStatus status, Integer arg2, Bundle data) { - - Message msg = Message.obtain(); - assert msg != null; - msg.arg1 = status.ordinal(); - if (arg2 != null) { - msg.arg2 = arg2; - } - if (data != null) { - msg.setData(data); - } - - try { - mMessenger.send(msg); - } catch (RemoteException e) { - Log.w(Constants.TAG, "Exception sending message, Is handler present?", e); - } catch (NullPointerException e) { - Log.w(Constants.TAG, "Messenger is null!", e); - } - } - - private void sendMessageToHandler(MessageStatus status, OperationResult data) { - Bundle bundle = new Bundle(); - bundle.putParcelable(OperationResult.EXTRA_RESULT, data); - sendMessageToHandler(status, null, bundle); - } - - private void sendMessageToHandler(MessageStatus status, Bundle data) { - sendMessageToHandler(status, null, data); - } - - private void sendMessageToHandler(MessageStatus status) { - sendMessageToHandler(status, null, null); - } - - /** - * Set progress of ProgressDialog by sending message to handler on UI thread - */ - public void setProgress(String message, int progress, int max) { - Log.d(Constants.TAG, "Send message by setProgress with progress=" + progress + ", max=" - + max); - - Bundle data = new Bundle(); - if (message != null) { - data.putString(ServiceProgressHandler.DATA_MESSAGE, message); - } - data.putInt(ServiceProgressHandler.DATA_PROGRESS, progress); - data.putInt(ServiceProgressHandler.DATA_PROGRESS_MAX, max); - - sendMessageToHandler(MessageStatus.UPDATE_PROGRESS, null, data); - } - - public void setProgress(int resourceId, int progress, int max) { - setProgress(getString(resourceId), progress, max); - } - - public void setProgress(int progress, int max) { - setProgress(null, progress, max); - } - - @Override - public void setPreventCancel() { - sendMessageToHandler(MessageStatus.PREVENT_CANCEL); - } - - private InputData createDecryptInputData(Bundle data) throws IOException, PgpGeneralException { - return createCryptInputData(data, DECRYPT_CIPHERTEXT_BYTES); - } - - private InputData createCryptInputData(Bundle data, String bytesName) throws PgpGeneralException, IOException { - int source = data.get(SOURCE) != null ? data.getInt(SOURCE) : data.getInt(TARGET); - IOType type = IOType.fromInt(source); - switch (type) { - case BYTES: /* encrypting bytes directly */ - byte[] bytes = data.getByteArray(bytesName); - return new InputData(new ByteArrayInputStream(bytes), bytes.length); - - case URI: /* encrypting content uri */ - Uri providerUri = data.getParcelable(ENCRYPT_DECRYPT_INPUT_URI); - - // InputStream - return new InputData(getContentResolver().openInputStream(providerUri), FileHelper.getFileSize(this, providerUri, 0)); - - default: - throw new PgpGeneralException("No target chosen!"); - } - } - - private OutputStream createCryptOutputStream(Bundle data) throws PgpGeneralException, FileNotFoundException { - int target = data.getInt(TARGET); - IOType type = IOType.fromInt(target); - switch (type) { - case BYTES: - return new ByteArrayOutputStream(); - - case URI: - Uri providerUri = data.getParcelable(ENCRYPT_DECRYPT_OUTPUT_URI); - - return getContentResolver().openOutputStream(providerUri); - - default: - throw new PgpGeneralException("No target chosen!"); - } - } - - private void finalizeDecryptOutputStream(Bundle data, Bundle resultData, OutputStream outStream) { - finalizeCryptOutputStream(data, resultData, outStream, RESULT_DECRYPTED_BYTES); - } - - private void finalizeCryptOutputStream(Bundle data, Bundle resultData, OutputStream outStream, String bytesName) { - int target = data.getInt(TARGET); - IOType type = IOType.fromInt(target); - switch (type) { - case BYTES: - byte output[] = ((ByteArrayOutputStream) outStream).toByteArray(); - resultData.putByteArray(bytesName, output); - break; - case URI: - // nothing, output was written, just send okay and verification bundle - - break; - } - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - if (ACTION_CANCEL.equals(intent.getAction())) { - mActionCanceled.set(true); - return START_NOT_STICKY; - } - return super.onStartCommand(intent, flags, startId); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java new file mode 100644 index 000000000..eff27f112 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * + * 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.service; + +import android.app.Service; +import android.content.Intent; +import android.os.Bundle; +import android.os.IBinder; +import android.os.Message; +import android.os.Messenger; +import android.os.Parcelable; +import android.os.RemoteException; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.operations.BaseOperation; +import org.sufficientlysecure.keychain.operations.CertifyOperation; +import org.sufficientlysecure.keychain.operations.ConsolidateOperation; +import org.sufficientlysecure.keychain.operations.DeleteOperation; +import org.sufficientlysecure.keychain.operations.EditKeyOperation; +import org.sufficientlysecure.keychain.operations.ExportOperation; +import org.sufficientlysecure.keychain.operations.ImportOperation; +import org.sufficientlysecure.keychain.operations.KeybaseVerificationOperation; +import org.sufficientlysecure.keychain.operations.PromoteKeyOperation; +import org.sufficientlysecure.keychain.operations.RevokeOperation; +import org.sufficientlysecure.keychain.operations.SignEncryptOperation; +import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation; +import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; +import org.sufficientlysecure.keychain.pgp.Progressable; +import org.sufficientlysecure.keychain.pgp.SignEncryptParcel; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.util.Log; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * This Service contains all important long lasting operations for OpenKeychain. It receives Intents with + * data from the activities or other apps, executes them, and stops itself after doing them. + */ +public class KeychainService extends Service implements Progressable { + + // messenger for communication (hack) + public static final String EXTRA_MESSENGER = "messenger"; + + // extras for operation + public static final String EXTRA_OPERATION_INPUT = "op_input"; + public static final String EXTRA_CRYPTO_INPUT = "crypto_input"; + + public static final String ACTION_CANCEL = "action_cancel"; + + // this attribute can possibly merged with the one above? not sure... + private AtomicBoolean mActionCanceled = new AtomicBoolean(false); + + ThreadLocal<Messenger> mMessenger = new ThreadLocal<>(); + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + /** + * This is run on the main thread, we need to spawn a runnable which runs on another thread for the actual operation + */ + @Override + public int onStartCommand(final Intent intent, int flags, int startId) { + + if (intent.getAction() != null && intent.getAction().equals(ACTION_CANCEL)) { + mActionCanceled.set(true); + return START_NOT_STICKY; + } + + Runnable actionRunnable = new Runnable() { + @Override + public void run() { + // We have not been cancelled! (yet) + mActionCanceled.set(false); + + Bundle extras = intent.getExtras(); + + // Set messenger for communication (for this particular thread) + mMessenger.set(extras.<Messenger>getParcelable(EXTRA_MESSENGER)); + + // Input + Parcelable inputParcel = extras.getParcelable(EXTRA_OPERATION_INPUT); + CryptoInputParcel cryptoInput = extras.getParcelable(EXTRA_CRYPTO_INPUT); + + // Operation + BaseOperation op; + + // just for brevity + KeychainService outerThis = KeychainService.this; + if (inputParcel instanceof SignEncryptParcel) { + op = new SignEncryptOperation(outerThis, new ProviderHelper(outerThis), + outerThis, mActionCanceled); + } else if (inputParcel instanceof PgpDecryptVerifyInputParcel) { + op = new PgpDecryptVerifyOperation(outerThis, new ProviderHelper(outerThis), outerThis); + } else if (inputParcel instanceof SaveKeyringParcel) { + op = new EditKeyOperation(outerThis, new ProviderHelper(outerThis), outerThis, + mActionCanceled); + } else if (inputParcel instanceof RevokeKeyringParcel) { + op = new RevokeOperation(outerThis, new ProviderHelper(outerThis), outerThis); + } else if (inputParcel instanceof CertifyActionsParcel) { + op = new CertifyOperation(outerThis, new ProviderHelper(outerThis), outerThis, + mActionCanceled); + } else if (inputParcel instanceof DeleteKeyringParcel) { + op = new DeleteOperation(outerThis, new ProviderHelper(outerThis), outerThis); + } else if (inputParcel instanceof PromoteKeyringParcel) { + op = new PromoteKeyOperation(outerThis, new ProviderHelper(outerThis), + outerThis, mActionCanceled); + } else if (inputParcel instanceof ImportKeyringParcel) { + op = new ImportOperation(outerThis, new ProviderHelper(outerThis), outerThis, + mActionCanceled); + } else if (inputParcel instanceof ExportKeyringParcel) { + op = new ExportOperation(outerThis, new ProviderHelper(outerThis), outerThis, + mActionCanceled); + } else if (inputParcel instanceof ConsolidateInputParcel) { + op = new ConsolidateOperation(outerThis, new ProviderHelper(outerThis), + outerThis); + } else if (inputParcel instanceof KeybaseVerificationParcel) { + op = new KeybaseVerificationOperation(outerThis, new ProviderHelper(outerThis), + outerThis); + } else { + throw new AssertionError("Unrecognized input parcel in KeychainService!"); + } + + @SuppressWarnings("unchecked") // this is unchecked, we make sure it's the correct op above! + OperationResult result = op.execute(inputParcel, cryptoInput); + sendMessageToHandler(MessageStatus.OKAY, result); + + } + }; + + Thread actionThread = new Thread(actionRunnable); + actionThread.start(); + + return START_NOT_STICKY; + } + + private void sendMessageToHandler(MessageStatus status, Integer arg2, Bundle data) { + + Message msg = Message.obtain(); + assert msg != null; + msg.arg1 = status.ordinal(); + if (arg2 != null) { + msg.arg2 = arg2; + } + if (data != null) { + msg.setData(data); + } + + try { + mMessenger.get().send(msg); + } catch (RemoteException e) { + Log.w(Constants.TAG, "Exception sending message, Is handler present?", e); + } catch (NullPointerException e) { + Log.w(Constants.TAG, "Messenger is null!", e); + } + } + + private void sendMessageToHandler(MessageStatus status, OperationResult data) { + Bundle bundle = new Bundle(); + bundle.putParcelable(OperationResult.EXTRA_RESULT, data); + sendMessageToHandler(status, null, bundle); + } + + private void sendMessageToHandler(MessageStatus status) { + sendMessageToHandler(status, null, null); + } + + /** + * 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); + + Bundle data = new Bundle(); + if (message != null) { + data.putString(ServiceProgressHandler.DATA_MESSAGE, message); + } + data.putInt(ServiceProgressHandler.DATA_PROGRESS, progress); + data.putInt(ServiceProgressHandler.DATA_PROGRESS_MAX, max); + + 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); + } + + @Override + public void setPreventCancel() { + sendMessageToHandler(MessageStatus.PREVENT_CANCEL); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java new file mode 100644 index 000000000..3243df1a8 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java @@ -0,0 +1,516 @@ +package org.sufficientlysecure.keychain.service; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.app.AlarmManager; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.AbstractThreadedSyncAdapter; +import android.content.ContentProviderClient; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.SyncResult; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.Messenger; +import android.os.PowerManager; +import android.os.SystemClock; +import android.support.v4.app.NotificationCompat; +import android.widget.Toast; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; +import org.sufficientlysecure.keychain.operations.ImportOperation; +import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.ui.OrbotRequiredDialogActivity; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.ParcelableProxy; +import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; + +import java.util.ArrayList; +import java.util.GregorianCalendar; +import java.util.Random; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +public class KeyserverSyncAdapterService extends Service { + + // how often a sync should be initiated, in s + public static final long SYNC_INTERVAL = + Constants.DEBUG_KEYSERVER_SYNC + ? TimeUnit.MINUTES.toSeconds(2) : TimeUnit.DAYS.toSeconds(3); + // time since last update after which a key should be updated again, in s + public static final long KEY_UPDATE_LIMIT = + Constants.DEBUG_KEYSERVER_SYNC ? 1 : TimeUnit.DAYS.toSeconds(7); + // time by which a sync is postponed in case of a + public static final long SYNC_POSTPONE_TIME = + Constants.DEBUG_KEYSERVER_SYNC ? 30 * 1000 : TimeUnit.MINUTES.toMillis(5); + // Time taken by Orbot before a new circuit is created + public static final int ORBOT_CIRCUIT_TIMEOUT = (int) TimeUnit.MINUTES.toMillis(10); + + + private static final String ACTION_IGNORE_TOR = "ignore_tor"; + private static final String ACTION_UPDATE_ALL = "update_all"; + private static final String ACTION_SYNC_NOW = "sync_now"; + private static final String ACTION_DISMISS_NOTIFICATION = "cancel_sync"; + private static final String ACTION_START_ORBOT = "start_orbot"; + private static final String ACTION_CANCEL = "cancel"; + + private AtomicBoolean mCancelled = new AtomicBoolean(false); + + @Override + public int onStartCommand(final Intent intent, int flags, final int startId) { + switch (intent.getAction()) { + case ACTION_CANCEL: { + mCancelled.set(true); + break; + } + // the reason for the separation betweyeen SYNC_NOW and UPDATE_ALL is so that starting + // the sync directly from the notification is possible while the screen is on with + // UPDATE_ALL, but a postponed sync is only started if screen is off + case ACTION_SYNC_NOW: { + // this checks for screen on/off before sync, and postpones the sync if on + ContentResolver.requestSync( + new Account(Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE), + Constants.PROVIDER_AUTHORITY, + new Bundle() + ); + break; + } + case ACTION_UPDATE_ALL: { + // does not check for screen on/off + asyncKeyUpdate(this, new CryptoInputParcel()); + break; + } + case ACTION_IGNORE_TOR: { + NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + manager.cancel(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT); + asyncKeyUpdate(this, new CryptoInputParcel(ParcelableProxy.getForNoProxy())); + break; + } + case ACTION_START_ORBOT: { + NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + manager.cancel(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT); + Intent startOrbot = new Intent(this, OrbotRequiredDialogActivity.class); + startOrbot.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startOrbot.putExtra(OrbotRequiredDialogActivity.EXTRA_START_ORBOT, true); + Messenger messenger = new Messenger( + new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case OrbotRequiredDialogActivity.MESSAGE_ORBOT_STARTED: { + asyncKeyUpdate(KeyserverSyncAdapterService.this, + new CryptoInputParcel()); + break; + } + case OrbotRequiredDialogActivity.MESSAGE_ORBOT_IGNORE: { + asyncKeyUpdate(KeyserverSyncAdapterService.this, + new CryptoInputParcel( + ParcelableProxy.getForNoProxy())); + break; + } + case OrbotRequiredDialogActivity.MESSAGE_DIALOG_CANCEL: { + // just stop service + stopSelf(); + break; + } + } + } + } + ); + startOrbot.putExtra(OrbotRequiredDialogActivity.EXTRA_MESSENGER, messenger); + startActivity(startOrbot); + break; + } + case ACTION_DISMISS_NOTIFICATION: { + NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + manager.cancel(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT); + stopSelf(startId); + break; + } + } + return START_NOT_STICKY; + } + + private class KeyserverSyncAdapter extends AbstractThreadedSyncAdapter { + + public KeyserverSyncAdapter() { + super(KeyserverSyncAdapterService.this, true); + } + + @Override + public void onPerformSync(Account account, Bundle extras, String authority, + ContentProviderClient provider, SyncResult syncResult) { + Log.d(Constants.TAG, "Performing a keyserver sync!"); + + PowerManager pm = (PowerManager) KeyserverSyncAdapterService.this + .getSystemService(Context.POWER_SERVICE); + @SuppressWarnings("deprecation") // our min is API 15, deprecated only in 20 + boolean isScreenOn = pm.isScreenOn(); + + if (!isScreenOn) { + Intent serviceIntent = new Intent(KeyserverSyncAdapterService.this, + KeyserverSyncAdapterService.class); + serviceIntent.setAction(ACTION_UPDATE_ALL); + startService(serviceIntent); + } else { + postponeSync(); + } + } + + @Override + public void onSyncCanceled() { + super.onSyncCanceled(); + cancelUpdates(KeyserverSyncAdapterService.this); + } + } + + @Override + public IBinder onBind(Intent intent) { + return new KeyserverSyncAdapter().getSyncAdapterBinder(); + } + + private void handleUpdateResult(ImportKeyResult result) { + if (result.isPending()) { + // result is pending due to Orbot not being started + // try to start it silently, if disabled show notifications + new OrbotHelper.SilentStartManager() { + @Override + protected void onOrbotStarted() { + // retry the update + asyncKeyUpdate(KeyserverSyncAdapterService.this, + new CryptoInputParcel()); + } + + @Override + protected void onSilentStartDisabled() { + // show notification + NotificationManager manager = + (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + manager.notify(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT, + getOrbotNoification(KeyserverSyncAdapterService.this)); + } + }.startOrbotAndListen(this, false); + } else if (isUpdateCancelled()) { + Log.d(Constants.TAG, "Keyserver sync cancelled, postponing by" + SYNC_POSTPONE_TIME + + "ms"); + postponeSync(); + } else { + Log.d(Constants.TAG, "Keyserver sync completed: Updated: " + result.mUpdatedKeys + + " Failed: " + result.mBadKeys); + stopSelf(); + } + } + + private void postponeSync() { + AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); + Intent serviceIntent = new Intent(this, KeyserverSyncAdapterService.class); + serviceIntent.setAction(ACTION_SYNC_NOW); + PendingIntent pi = PendingIntent.getService(this, 0, serviceIntent, + PendingIntent.FLAG_UPDATE_CURRENT); + alarmManager.set( + AlarmManager.ELAPSED_REALTIME_WAKEUP, + SystemClock.elapsedRealtime() + SYNC_POSTPONE_TIME, + pi + ); + } + + private void asyncKeyUpdate(final Context context, + final CryptoInputParcel cryptoInputParcel) { + new Thread(new Runnable() { + @Override + public void run() { + ImportKeyResult result = updateKeysFromKeyserver(context, cryptoInputParcel); + handleUpdateResult(result); + } + }).start(); + } + + private synchronized ImportKeyResult updateKeysFromKeyserver(final Context context, + final CryptoInputParcel cryptoInputParcel) { + mCancelled.set(false); + + ArrayList<ParcelableKeyRing> keyList = getKeysToUpdate(context); + + if (isUpdateCancelled()) { // if we've already been cancelled + return new ImportKeyResult(OperationResult.RESULT_CANCELLED, + new OperationResult.OperationLog()); + } + + if (cryptoInputParcel.getParcelableProxy() == null) { + // no explicit proxy, retrieve from preferences. Check if we should do a staggered sync + if (Preferences.getPreferences(context).getProxyPrefs().torEnabled) { + return staggeredUpdate(context, keyList, cryptoInputParcel); + } else { + return directUpdate(context, keyList, cryptoInputParcel); + } + } else { + return directUpdate(context, keyList, cryptoInputParcel); + } + } + + private ImportKeyResult directUpdate(Context context, ArrayList<ParcelableKeyRing> keyList, + CryptoInputParcel cryptoInputParcel) { + Log.d(Constants.TAG, "Starting normal update"); + ImportOperation importOp = new ImportOperation(context, new ProviderHelper(context), null); + return importOp.execute( + new ImportKeyringParcel(keyList, + Preferences.getPreferences(context).getPreferredKeyserver()), + cryptoInputParcel + ); + } + + + /** + * will perform a staggered update of user's keys using delays to ensure new Tor circuits, as + * performed by parcimonie. Relevant issue and method at: + * https://github.com/open-keychain/open-keychain/issues/1337 + * + * @return result of the sync + */ + private ImportKeyResult staggeredUpdate(Context context, ArrayList<ParcelableKeyRing> keyList, + CryptoInputParcel cryptoInputParcel) { + Log.d(Constants.TAG, "Starting staggered update"); + // final int WEEK_IN_SECONDS = (int) TimeUnit.DAYS.toSeconds(7); + final int WEEK_IN_SECONDS = 0; + ImportOperation.KeyImportAccumulator accumulator + = new ImportOperation.KeyImportAccumulator(keyList.size(), null); + for (ParcelableKeyRing keyRing : keyList) { + int waitTime; + int staggeredTime = new Random().nextInt(1 + 2 * (WEEK_IN_SECONDS / keyList.size())); + if (staggeredTime >= ORBOT_CIRCUIT_TIMEOUT) { + waitTime = staggeredTime; + } else { + waitTime = ORBOT_CIRCUIT_TIMEOUT + new Random().nextInt(ORBOT_CIRCUIT_TIMEOUT); + } + Log.d(Constants.TAG, "Updating key with fingerprint " + keyRing.mExpectedFingerprint + + " with a wait time of " + waitTime + "s"); + try { + Thread.sleep(waitTime * 1000); + } catch (InterruptedException e) { + Log.e(Constants.TAG, "Exception during sleep between key updates", e); + // skip this one + continue; + } + ArrayList<ParcelableKeyRing> keyWrapper = new ArrayList<>(); + keyWrapper.add(keyRing); + if (isUpdateCancelled()) { + return new ImportKeyResult(ImportKeyResult.RESULT_CANCELLED, + new OperationResult.OperationLog()); + } + ImportKeyResult result = + new ImportOperation(context, new ProviderHelper(context), null, mCancelled) + .execute( + new ImportKeyringParcel( + keyWrapper, + Preferences.getPreferences(context) + .getPreferredKeyserver() + ), + cryptoInputParcel + ); + if (result.isPending()) { + return result; + } + accumulator.accumulateKeyImport(result); + } + return accumulator.getConsolidatedResult(); + } + + /** + * 1. Get keys which have been updated recently and therefore do not need to + * be updated now + * 2. Get list of all keys and filter out ones that don't need to be updated + * 3. Return keys to be updated + * + * @return list of keys that require update + */ + private ArrayList<ParcelableKeyRing> getKeysToUpdate(Context context) { + + // 1. Get keys which have been updated recently and don't need to updated now + final int INDEX_UPDATED_KEYS_MASTER_KEY_ID = 0; + final int INDEX_LAST_UPDATED = 1; + + // all time in seconds not milliseconds + final long CURRENT_TIME = GregorianCalendar.getInstance().getTimeInMillis() / 1000; + Cursor updatedKeysCursor = context.getContentResolver().query( + KeychainContract.UpdatedKeys.CONTENT_URI, + new String[]{ + KeychainContract.UpdatedKeys.MASTER_KEY_ID, + KeychainContract.UpdatedKeys.LAST_UPDATED + }, + "? - " + KeychainContract.UpdatedKeys.LAST_UPDATED + " < " + KEY_UPDATE_LIMIT, + new String[]{"" + CURRENT_TIME}, + null + ); + + ArrayList<Long> ignoreMasterKeyIds = new ArrayList<>(); + while (updatedKeysCursor.moveToNext()) { + long masterKeyId = updatedKeysCursor.getLong(INDEX_UPDATED_KEYS_MASTER_KEY_ID); + Log.d(Constants.TAG, "Keyserver sync: Ignoring {" + masterKeyId + "} last updated at {" + + updatedKeysCursor.getLong(INDEX_LAST_UPDATED) + "}s"); + ignoreMasterKeyIds.add(masterKeyId); + } + updatedKeysCursor.close(); + + // 2. Make a list of public keys which should be updated + final int INDEX_MASTER_KEY_ID = 0; + final int INDEX_FINGERPRINT = 1; + Cursor keyCursor = context.getContentResolver().query( + KeychainContract.KeyRings.buildUnifiedKeyRingsUri(), + new String[]{ + KeychainContract.KeyRings.MASTER_KEY_ID, + KeychainContract.KeyRings.FINGERPRINT + }, + null, + null, + null + ); + + if (keyCursor == null) { + return new ArrayList<>(); + } + + ArrayList<ParcelableKeyRing> keyList = new ArrayList<>(); + while (keyCursor.moveToNext()) { + long keyId = keyCursor.getLong(INDEX_MASTER_KEY_ID); + if (ignoreMasterKeyIds.contains(keyId)) { + continue; + } + Log.d(Constants.TAG, "Keyserver sync: Updating {" + keyId + "}"); + String fingerprint = KeyFormattingUtils + .convertFingerprintToHex(keyCursor.getBlob(INDEX_FINGERPRINT)); + String hexKeyId = KeyFormattingUtils + .convertKeyIdToHex(keyId); + // we aren't updating from keybase as of now + keyList.add(new ParcelableKeyRing(fingerprint, hexKeyId, null)); + } + keyCursor.close(); + + return keyList; + } + + private boolean isUpdateCancelled() { + return mCancelled.get(); + } + + /** + * will cancel an update already in progress. We send an Intent to cancel it instead of simply + * modifying a static variable sync the service is running in a process that is different from + * the default application process where the UI code runs. + * + * @param context used to send an Intent to the service requesting cancellation. + */ + public static void cancelUpdates(Context context) { + Intent intent = new Intent(context, KeyserverSyncAdapterService.class); + intent.setAction(ACTION_CANCEL); + context.startService(intent); + } + + private Notification getOrbotNoification(Context context) { + NotificationCompat.Builder builder = new NotificationCompat.Builder(context); + builder.setSmallIcon(R.drawable.ic_stat_notify_24dp) + .setLargeIcon(getBitmap(R.drawable.ic_launcher, context)) + .setContentTitle(context.getString(R.string.keyserver_sync_orbot_notif_title)) + .setContentText(context.getString(R.string.keyserver_sync_orbot_notif_msg)) + .setAutoCancel(true); + + // In case the user decides to not use tor + Intent ignoreTorIntent = new Intent(context, KeyserverSyncAdapterService.class); + ignoreTorIntent.setAction(ACTION_IGNORE_TOR); + PendingIntent ignoreTorPi = PendingIntent.getService( + context, + 0, // security not issue since we're giving this pending intent to Notification Manager + ignoreTorIntent, + PendingIntent.FLAG_CANCEL_CURRENT + ); + + builder.addAction(R.drawable.ic_stat_tor_off, + context.getString(R.string.keyserver_sync_orbot_notif_ignore), + ignoreTorPi); + + Intent startOrbotIntent = new Intent(context, KeyserverSyncAdapterService.class); + startOrbotIntent.setAction(ACTION_START_ORBOT); + PendingIntent startOrbotPi = PendingIntent.getService( + context, + 0, // security not issue since we're giving this pending intent to Notification Manager + startOrbotIntent, + PendingIntent.FLAG_CANCEL_CURRENT + ); + + builder.addAction(R.drawable.ic_stat_tor, + context.getString(R.string.keyserver_sync_orbot_notif_start), + startOrbotPi + ); + builder.setContentIntent(startOrbotPi); + + return builder.build(); + } + + public static void enableKeyserverSync(Context context) { + try { + AccountManager manager = AccountManager.get(context); + Account[] accounts = manager.getAccountsByType(Constants.ACCOUNT_TYPE); + + Account account = new Account(Constants.ACCOUNT_NAME, Constants.ACCOUNT_TYPE); + if (accounts.length == 0) { + if (!manager.addAccountExplicitly(account, null, null)) { + Log.e(Constants.TAG, "Adding account failed!"); + } + } + // for keyserver sync + ContentResolver.setIsSyncable(account, Constants.PROVIDER_AUTHORITY, 1); + ContentResolver.setSyncAutomatically(account, Constants.PROVIDER_AUTHORITY, + true); + ContentResolver.addPeriodicSync( + account, + Constants.PROVIDER_AUTHORITY, + new Bundle(), + SYNC_INTERVAL + ); + } catch (SecurityException e) { + Log.e(Constants.TAG, "SecurityException when adding the account", e); + Toast.makeText(context, R.string.reinstall_openkeychain, Toast.LENGTH_LONG).show(); + } + } + + // from de.azapps.mirakel.helper.Helpers from https://github.com/MirakelX/mirakel-android + private Bitmap getBitmap(int resId, Context context) { + int mLargeIconWidth = (int) context.getResources().getDimension( + android.R.dimen.notification_large_icon_width); + int mLargeIconHeight = (int) context.getResources().getDimension( + android.R.dimen.notification_large_icon_height); + Drawable d; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + // noinspection deprecation (can't help it at this api level) + d = context.getResources().getDrawable(resId); + } else { + d = context.getDrawable(resId); + } + if (d == null) { + return null; + } + Bitmap b = Bitmap.createBitmap(mLargeIconWidth, mLargeIconHeight, Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(b); + d.setBounds(0, 0, mLargeIconWidth, mLargeIconHeight); + d.draw(c); + return b; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java index 78137170d..be269c66d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java @@ -17,6 +17,9 @@ package org.sufficientlysecure.keychain.service; + +import java.util.Date; + import android.app.AlarmManager; import android.app.Notification; import android.app.PendingIntent; @@ -25,8 +28,13 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; import android.os.Binder; import android.os.Build; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; @@ -46,8 +54,6 @@ import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Preferences; -import java.util.Date; - /** * 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 @@ -95,8 +101,6 @@ public class PassphraseCacheService extends Service { private static final long DEFAULT_TTL = 15; - private static final int NOTIFICATION_ID = 1; - private static final int MSG_PASSPHRASE_CACHE_GET_OKAY = 1; private static final int MSG_PASSPHRASE_CACHE_GET_KEY_NOT_FOUND = 2; @@ -149,6 +153,14 @@ public class PassphraseCacheService extends Service { context.startService(intent); } + public static void clearCachedPassphrases(Context context) { + Log.d(Constants.TAG, "PassphraseCacheService.clearCachedPassphrase()"); + + Intent intent = new Intent(context, PassphraseCacheService.class); + intent.setAction(ACTION_PASSPHRASE_CACHE_CLEAR); + + context.startService(intent); + } /** * Gets a cached passphrase from memory by sending an intent to the service. This method is @@ -218,21 +230,21 @@ public class PassphraseCacheService extends Service { * Internal implementation to get cached passphrase. */ private Passphrase getCachedPassphraseImpl(long masterKeyId, long subKeyId) throws ProviderHelper.NotFoundException { + // on "none" key, just do nothing + if (masterKeyId == Constants.key.none) { + return null; + } + // passphrase for symmetric encryption? if (masterKeyId == Constants.key.symmetric) { Log.d(Constants.TAG, "PassphraseCacheService.getCachedPassphraseImpl() for symmetric encryption"); - Passphrase cachedPassphrase = mPassphraseCache.get(Constants.key.symmetric).getPassphrase(); + CachedPassphrase cachedPassphrase = mPassphraseCache.get(Constants.key.symmetric); if (cachedPassphrase == null) { return null; } addCachedPassphrase(this, Constants.key.symmetric, Constants.key.symmetric, - cachedPassphrase, getString(R.string.passp_cache_notif_pwd)); - return cachedPassphrase; - } - - // on "none" key, just do nothing - if (masterKeyId == Constants.key.none) { - return null; + cachedPassphrase.getPassphrase(), getString(R.string.passp_cache_notif_pwd)); + return cachedPassphrase.getPassphrase(); } // try to get master key id which is used as an identifier for cached passphrases @@ -309,7 +321,7 @@ public class PassphraseCacheService extends Service { if (action.equals(BROADCAST_ACTION_PASSPHRASE_CACHE_SERVICE)) { long keyId = intent.getLongExtra(EXTRA_KEY_ID, -1); - timeout(context, keyId); + timeout(keyId); } } }; @@ -411,9 +423,9 @@ public class PassphraseCacheService extends Service { long referenceKeyId; if (Preferences.getPreferences(mContext).getPassphraseCacheSubs()) { - referenceKeyId = intent.getLongExtra(EXTRA_KEY_ID, 0L); - } else { referenceKeyId = intent.getLongExtra(EXTRA_SUBKEY_ID, 0L); + } else { + referenceKeyId = intent.getLongExtra(EXTRA_KEY_ID, 0L); } // Stop specific ttl alarm and am.cancel(buildIntent(this, referenceKeyId)); @@ -444,12 +456,17 @@ public class PassphraseCacheService extends Service { /** * Called when one specific passphrase for keyId timed out */ - private void timeout(Context context, long keyId) { + private void timeout(long keyId) { + CachedPassphrase cPass = mPassphraseCache.get(keyId); - // clean internal char[] from memory! - cPass.getPassphrase().removeFromMemory(); - // remove passphrase object - mPassphraseCache.remove(keyId); + if (cPass != null) { + if (cPass.getPassphrase() != null) { + // clean internal char[] from memory! + cPass.getPassphrase().removeFromMemory(); + } + // remove passphrase object + mPassphraseCache.remove(keyId); + } Log.d(Constants.TAG, "PassphraseCacheService Timeout of keyId " + keyId + ", removed from memory!"); @@ -458,7 +475,7 @@ public class PassphraseCacheService extends Service { private void updateService() { if (mPassphraseCache.size() > 0) { - startForeground(NOTIFICATION_ID, getNotification()); + startForeground(Constants.Notification.PASSPHRASE_CACHE, getNotification()); } else { // stop whole service if no cached passphrases remaining Log.d(Constants.TAG, "PassphraseCacheService: No passphrases remaining in memory, stopping service!"); @@ -466,59 +483,67 @@ public class PassphraseCacheService extends Service { } } + // from de.azapps.mirakel.helper.Helpers from https://github.com/MirakelX/mirakel-android + private static Bitmap getBitmap(int resId, Context context) { + int mLargeIconWidth = (int) context.getResources().getDimension( + android.R.dimen.notification_large_icon_width); + int mLargeIconHeight = (int) context.getResources().getDimension( + android.R.dimen.notification_large_icon_height); + Drawable d; + if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) { + // noinspection deprecation (can't help it at this api level) + d = context.getResources().getDrawable(resId); + } else { + d = context.getDrawable(resId); + } + if (d == null) { + return null; + } + Bitmap b = Bitmap.createBitmap(mLargeIconWidth, mLargeIconHeight, Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(b); + d.setBounds(0, 0, mLargeIconWidth, mLargeIconHeight); + d.draw(c); + return b; + } + private Notification getNotification() { NotificationCompat.Builder builder = new NotificationCompat.Builder(this); + builder.setSmallIcon(R.drawable.ic_stat_notify_24dp) + .setLargeIcon(getBitmap(R.drawable.ic_launcher, getBaseContext())) + .setContentTitle(getResources().getQuantityString(R.plurals.passp_cache_notif_n_keys, + mPassphraseCache.size(), mPassphraseCache.size())) + .setContentText(getString(R.string.passp_cache_notif_click_to_clear)); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - builder.setSmallIcon(R.drawable.ic_launcher) - .setContentTitle(getString(R.string.app_name)) - .setContentText(String.format(getString(R.string.passp_cache_notif_n_keys), - mPassphraseCache.size())); + NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle(); - NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle(); + inboxStyle.setBigContentTitle(getString(R.string.passp_cache_notif_keys)); - inboxStyle.setBigContentTitle(getString(R.string.passp_cache_notif_keys)); + // Moves events into the big view + for (int i = 0; i < mPassphraseCache.size(); i++) { + inboxStyle.addLine(mPassphraseCache.valueAt(i).getPrimaryUserID()); + } - // Moves events into the big view - for (int i = 0; i < mPassphraseCache.size(); i++) { - inboxStyle.addLine(mPassphraseCache.valueAt(i).getPrimaryUserID()); - } + // Moves the big view style object into the notification object. + builder.setStyle(inboxStyle); - // Moves the big view style object into the notification object. - builder.setStyle(inboxStyle); - - // Add purging action - Intent intent = new Intent(getApplicationContext(), PassphraseCacheService.class); - intent.setAction(ACTION_PASSPHRASE_CACHE_CLEAR); - builder.addAction( - R.drawable.abc_ic_clear_mtrl_alpha, - getString(R.string.passp_cache_notif_clear), - PendingIntent.getService( - getApplicationContext(), - 0, - intent, - PendingIntent.FLAG_UPDATE_CURRENT - ) - ); - } else { - // Fallback, since expandable notifications weren't available back then - builder.setSmallIcon(R.drawable.ic_launcher) - .setContentTitle(String.format(getString(R.string.passp_cache_notif_n_keys), - mPassphraseCache.size())) - .setContentText(getString(R.string.passp_cache_notif_click_to_clear)); - - Intent intent = new Intent(getApplicationContext(), PassphraseCacheService.class); - intent.setAction(ACTION_PASSPHRASE_CACHE_CLEAR); - - builder.setContentIntent( - PendingIntent.getService( - getApplicationContext(), - 0, - intent, - PendingIntent.FLAG_UPDATE_CURRENT - ) - ); - } + Intent intent = new Intent(getApplicationContext(), PassphraseCacheService.class); + intent.setAction(ACTION_PASSPHRASE_CACHE_CLEAR); + PendingIntent clearCachePi = PendingIntent.getService( + getApplicationContext(), + 0, + intent, + PendingIntent.FLAG_UPDATE_CURRENT + ); + + // Add cache clear PI to normal touch + builder.setContentIntent(clearCachePi); + + // Add clear PI action below text + builder.addAction( + R.drawable.abc_ic_clear_mtrl_alpha, + getString(R.string.passp_cache_notif_clear), + clearCachePi + ); return builder.build(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PromoteKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PromoteKeyringParcel.java new file mode 100644 index 000000000..d268c3694 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PromoteKeyringParcel.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.com> + * + * 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.service; + +import android.os.Parcel; +import android.os.Parcelable; + +public class PromoteKeyringParcel implements Parcelable { + + public long mKeyRingId; + public byte[] mCardAid; + public long[] mSubKeyIds; + + public PromoteKeyringParcel(long keyRingId, byte[] cardAid, long[] subKeyIds) { + mKeyRingId = keyRingId; + mCardAid = cardAid; + mSubKeyIds = subKeyIds; + } + + protected PromoteKeyringParcel(Parcel in) { + mKeyRingId = in.readLong(); + mCardAid = in.createByteArray(); + mSubKeyIds = in.createLongArray(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(mKeyRingId); + dest.writeByteArray(mCardAid); + dest.writeLongArray(mSubKeyIds); + } + + public static final Parcelable.Creator<PromoteKeyringParcel> CREATOR = new Parcelable.Creator<PromoteKeyringParcel>() { + @Override + public PromoteKeyringParcel createFromParcel(Parcel in) { + return new PromoteKeyringParcel(in); + } + + @Override + public PromoteKeyringParcel[] newArray(int size) { + return new PromoteKeyringParcel[size]; + } + }; +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/RevokeKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/RevokeKeyringParcel.java new file mode 100644 index 000000000..02e8fda46 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/RevokeKeyringParcel.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2013-2015 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.com> + * + * 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.service; + +import android.os.Parcel; +import android.os.Parcelable; + +public class RevokeKeyringParcel implements Parcelable { + + final public long mMasterKeyId; + final public boolean mUpload; + final public String mKeyserver; + + public RevokeKeyringParcel(long masterKeyId, boolean upload, String keyserver) { + mMasterKeyId = masterKeyId; + mUpload = upload; + mKeyserver = keyserver; + } + + protected RevokeKeyringParcel(Parcel in) { + mMasterKeyId = in.readLong(); + mUpload = in.readByte() != 0x00; + mKeyserver = in.readString(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(mMasterKeyId); + dest.writeByte((byte) (mUpload ? 0x01 : 0x00)); + dest.writeString(mKeyserver); + } + + public static final Parcelable.Creator<RevokeKeyringParcel> CREATOR = new Parcelable.Creator<RevokeKeyringParcel>() { + @Override + public RevokeKeyringParcel createFromParcel(Parcel in) { + return new RevokeKeyringParcel(in); + } + + @Override + public RevokeKeyringParcel[] newArray(int size) { + return new RevokeKeyringParcel[size]; + } + }; +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java index 2e0524141..6959fca56 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java @@ -61,6 +61,15 @@ public class SaveKeyringParcel implements Parcelable { public ArrayList<String> mRevokeUserIds; public ArrayList<Long> mRevokeSubKeys; + // if these are non-null, PINs will be changed on the card + public Passphrase mCardPin; + public Passphrase mCardAdminPin; + + // private because they have to be set together with setUpdateOptions + private boolean mUpload; + private boolean mUploadAtomic; + private String mKeyserver; + public SaveKeyringParcel() { reset(); } @@ -80,6 +89,29 @@ public class SaveKeyringParcel implements Parcelable { mChangeSubKeys = new ArrayList<>(); mRevokeUserIds = new ArrayList<>(); mRevokeSubKeys = new ArrayList<>(); + mCardPin = null; + mCardAdminPin = null; + mUpload = false; + mUploadAtomic = false; + mKeyserver = null; + } + + public void setUpdateOptions(boolean upload, boolean uploadAtomic, String keysever) { + mUpload = upload; + mUploadAtomic = uploadAtomic; + mKeyserver = keysever; + } + + public boolean isUpload() { + return mUpload; + } + + public boolean isUploadAtomic() { + return mUploadAtomic; + } + + public String getUploadKeyserver() { + return mKeyserver; } public boolean isEmpty() { @@ -95,7 +127,8 @@ public class SaveKeyringParcel implements Parcelable { } for (SubkeyChange change : mChangeSubKeys) { - if (change.mRecertify || change.mFlags != null || change.mExpiry != null) { + if (change.mRecertify || change.mFlags != null || change.mExpiry != null + || change.mMoveKeyToCard) { return false; } } @@ -142,6 +175,8 @@ public class SaveKeyringParcel implements Parcelable { public boolean mRecertify; // if this flag is true, the subkey should be changed to a stripped key public boolean mDummyStrip; + // if this flag is true, the subkey should be moved to a card + public boolean mMoveKeyToCard; // if this is non-null, the subkey will be changed to a divert-to-card // key for the given serial number public byte[] mDummyDivert; @@ -161,16 +196,16 @@ public class SaveKeyringParcel implements Parcelable { mExpiry = expiry; } - public SubkeyChange(long keyId, boolean dummyStrip, byte[] dummyDivert) { + public SubkeyChange(long keyId, boolean dummyStrip, boolean moveKeyToCard) { this(keyId, null, null); // these flags are mutually exclusive! - if (dummyStrip && dummyDivert != null) { + if (dummyStrip && moveKeyToCard) { throw new AssertionError( - "cannot set strip and divert flags at the same time - this is a bug!"); + "cannot set strip and keytocard flags at the same time - this is a bug!"); } mDummyStrip = dummyStrip; - mDummyDivert = dummyDivert; + mMoveKeyToCard = moveKeyToCard; } @Override @@ -179,6 +214,7 @@ public class SaveKeyringParcel implements Parcelable { out += "mFlags: " + mFlags + ", "; out += "mExpiry: " + mExpiry + ", "; out += "mDummyStrip: " + mDummyStrip + ", "; + out += "mMoveKeyToCard: " + mMoveKeyToCard + ", "; out += "mDummyDivert: [" + (mDummyDivert == null ? 0 : mDummyDivert.length) + " bytes]"; return out; @@ -206,6 +242,7 @@ public class SaveKeyringParcel implements Parcelable { } } + @SuppressWarnings("unchecked") // we verify the reads against writes in writeToParcel public SaveKeyringParcel(Parcel source) { mMasterKeyId = source.readInt() != 0 ? source.readLong() : null; mFingerprint = source.createByteArray(); @@ -221,6 +258,13 @@ public class SaveKeyringParcel implements Parcelable { mRevokeUserIds = source.createStringArrayList(); mRevokeSubKeys = (ArrayList<Long>) source.readSerializable(); + + mCardPin = source.readParcelable(Passphrase.class.getClassLoader()); + mCardAdminPin = source.readParcelable(Passphrase.class.getClassLoader()); + + mUpload = source.readByte() != 0; + mUploadAtomic = source.readByte() != 0; + mKeyserver = source.readString(); } @Override @@ -232,7 +276,7 @@ public class SaveKeyringParcel implements Parcelable { destination.writeByteArray(mFingerprint); // yes, null values are ok for parcelables - destination.writeParcelable(mNewUnlock, 0); + destination.writeParcelable(mNewUnlock, flags); destination.writeStringList(mAddUserIds); destination.writeSerializable(mAddUserAttribute); @@ -243,6 +287,13 @@ public class SaveKeyringParcel implements Parcelable { destination.writeStringList(mRevokeUserIds); destination.writeSerializable(mRevokeSubKeys); + + destination.writeParcelable(mCardPin, flags); + destination.writeParcelable(mCardAdminPin, flags); + + destination.writeByte((byte) (mUpload ? 1 : 0)); + destination.writeByte((byte) (mUploadAtomic ? 1 : 0)); + destination.writeString(mKeyserver); } public static final Creator<SaveKeyringParcel> CREATOR = new Creator<SaveKeyringParcel>() { @@ -270,7 +321,9 @@ public class SaveKeyringParcel implements Parcelable { out += "mChangeSubKeys: " + mChangeSubKeys + "\n"; out += "mChangePrimaryUserId: " + mChangePrimaryUserId + "\n"; out += "mRevokeUserIds: " + mRevokeUserIds + "\n"; - out += "mRevokeSubKeys: " + mRevokeSubKeys; + out += "mRevokeSubKeys: " + mRevokeSubKeys + "\n"; + out += "mCardPin: " + mCardPin + "\n"; + out += "mCardAdminPin: " + mCardAdminPin; return out; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ServiceProgressHandler.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ServiceProgressHandler.java index 430d8a49b..d294e5057 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ServiceProgressHandler.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ServiceProgressHandler.java @@ -17,8 +17,8 @@ package org.sufficientlysecure.keychain.service; -import android.app.Activity; -import android.content.Intent; + +import android.app.ProgressDialog; import android.os.Bundle; import android.os.Handler; import android.os.Message; @@ -27,7 +27,6 @@ import android.support.v4.app.FragmentManager; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.operations.results.CertifyResult; import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; @@ -35,7 +34,7 @@ import org.sufficientlysecure.keychain.util.Log; public class ServiceProgressHandler extends Handler { // possible messages sent from this service to handler on ui - public static enum MessageStatus{ + public enum MessageStatus { UNKNOWN, OKAY, EXCEPTION, @@ -44,9 +43,8 @@ public class ServiceProgressHandler extends Handler { private static final MessageStatus[] values = values(); - public static MessageStatus fromInt(int n) - { - if(n < 0 || n >= values.length) { + public static MessageStatus fromInt(int n) { + if (n < 0 || n >= values.length) { return UNKNOWN; } else { return values[n]; @@ -66,74 +64,51 @@ public class ServiceProgressHandler extends Handler { public static final String KEYBASE_PRESENCE_URL = "keybase_presence_url"; public static final String KEYBASE_PRESENCE_LABEL = "keybase_presence_label"; - Activity mActivity; - ProgressDialogFragment mProgressDialogFragment; + public static final String TAG_PROGRESS_DIALOG = "progressDialog"; - public ServiceProgressHandler(Activity activity) { - this.mActivity = activity; - } + FragmentActivity mActivity; - public ServiceProgressHandler(Activity activity, - ProgressDialogFragment progressDialogFragment) { - this.mActivity = activity; - this.mProgressDialogFragment = progressDialogFragment; + public ServiceProgressHandler(FragmentActivity activity) { + mActivity = activity; } - public ServiceProgressHandler(Activity activity, - String progressDialogMessage, - int progressDialogStyle, - ProgressDialogFragment.ServiceType serviceType) { - this(activity, progressDialogMessage, progressDialogStyle, false, serviceType); + public void showProgressDialog() { + showProgressDialog("", ProgressDialog.STYLE_SPINNER, false); } - public ServiceProgressHandler(Activity activity, - String progressDialogMessage, - int progressDialogStyle, - boolean cancelable, - ProgressDialogFragment.ServiceType serviceType) { - this.mActivity = activity; - this.mProgressDialogFragment = ProgressDialogFragment.newInstance( + public void showProgressDialog( + String progressDialogMessage, int progressDialogStyle, boolean cancelable) { + + final ProgressDialogFragment frag = ProgressDialogFragment.newInstance( progressDialogMessage, progressDialogStyle, - cancelable, - serviceType); - } - - public void showProgressDialog(FragmentActivity activity) { - if (mProgressDialogFragment == null) { - return; - } + cancelable); // TODO: This is a hack!, see // http://stackoverflow.com/questions/10114324/show-dialogfragment-from-onactivityresult - final FragmentManager manager = activity.getSupportFragmentManager(); + final FragmentManager manager = mActivity.getSupportFragmentManager(); Handler handler = new Handler(); handler.post(new Runnable() { public void run() { - mProgressDialogFragment.show(manager, "progressDialog"); + frag.show(manager, TAG_PROGRESS_DIALOG); } }); + } @Override public void handleMessage(Message message) { Bundle data = message.getData(); - if (mProgressDialogFragment == null) { - // Log.e(Constants.TAG, - // "Progress has not been updated because mProgressDialogFragment was null!"); - return; - } - MessageStatus status = MessageStatus.fromInt(message.arg1); switch (status) { case OKAY: - mProgressDialogFragment.dismissAllowingStateLoss(); + dismissAllowingStateLoss(); break; case EXCEPTION: - mProgressDialogFragment.dismissAllowingStateLoss(); + dismissAllowingStateLoss(); // show error from service if (data.containsKey(DATA_ERROR)) { @@ -147,23 +122,25 @@ public class ServiceProgressHandler extends Handler { case UPDATE_PROGRESS: if (data.containsKey(DATA_PROGRESS) && data.containsKey(DATA_PROGRESS_MAX)) { + String msg = null; + int progress = data.getInt(DATA_PROGRESS); + int max = data.getInt(DATA_PROGRESS_MAX); + // update progress from service if (data.containsKey(DATA_MESSAGE)) { - mProgressDialogFragment.setProgress(data.getString(DATA_MESSAGE), - data.getInt(DATA_PROGRESS), data.getInt(DATA_PROGRESS_MAX)); + msg = data.getString(DATA_MESSAGE); } else if (data.containsKey(DATA_MESSAGE_ID)) { - mProgressDialogFragment.setProgress(data.getInt(DATA_MESSAGE_ID), - data.getInt(DATA_PROGRESS), data.getInt(DATA_PROGRESS_MAX)); - } else { - mProgressDialogFragment.setProgress(data.getInt(DATA_PROGRESS), - data.getInt(DATA_PROGRESS_MAX)); + msg = mActivity.getString(data.getInt(DATA_MESSAGE_ID)); } + + onSetProgress(msg, progress, max); + } break; case PREVENT_CANCEL: - mProgressDialogFragment.setPreventCancel(true); + setPreventCancel(true); break; default: @@ -171,4 +148,48 @@ public class ServiceProgressHandler extends Handler { break; } } + + private void setPreventCancel(boolean preventCancel) { + ProgressDialogFragment progressDialogFragment = + (ProgressDialogFragment) mActivity.getSupportFragmentManager() + .findFragmentByTag("progressDialog"); + + if (progressDialogFragment == null) { + return; + } + + progressDialogFragment.setPreventCancel(preventCancel); + } + + protected void dismissAllowingStateLoss() { + ProgressDialogFragment progressDialogFragment = + (ProgressDialogFragment) mActivity.getSupportFragmentManager() + .findFragmentByTag("progressDialog"); + + if (progressDialogFragment == null) { + return; + } + + progressDialogFragment.dismissAllowingStateLoss(); + } + + + protected void onSetProgress(String msg, int progress, int max) { + + ProgressDialogFragment progressDialogFragment = + (ProgressDialogFragment) mActivity.getSupportFragmentManager() + .findFragmentByTag("progressDialog"); + + if (progressDialogFragment == null) { + return; + } + + if (msg != null) { + progressDialogFragment.setProgress(msg, progress, max); + } else { + progressDialogFragment.setProgress(progress, max); + } + + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java index 3d1ccaca1..0d8569fe6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java @@ -17,52 +17,87 @@ package org.sufficientlysecure.keychain.service.input; -import java.nio.ByteBuffer; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - import android.os.Parcel; import android.os.Parcelable; +import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.Passphrase; +import java.nio.ByteBuffer; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + /** * This is a base class for the input of crypto operations. */ public class CryptoInputParcel implements Parcelable { - final Date mSignatureTime; - final Passphrase mPassphrase; + private Date mSignatureTime; + private boolean mHasSignature; + + public Passphrase mPassphrase; + // used to supply an explicit proxy to operations that require it + // this is not final so it can be added to an existing CryptoInputParcel + // (e.g) CertifyOperation with upload might require both passphrase and orbot to be enabled + private ParcelableProxy mParcelableProxy; + + // specifies whether passphrases should be cached + public boolean mCachePassphrase = true; // this map contains both decrypted session keys and signed hashes to be // used in the crypto operation described by this parcel. private HashMap<ByteBuffer, byte[]> mCryptoData = new HashMap<>(); public CryptoInputParcel() { - mSignatureTime = new Date(); + mSignatureTime = null; mPassphrase = null; + mCachePassphrase = true; } public CryptoInputParcel(Date signatureTime, Passphrase passphrase) { + mHasSignature = true; mSignatureTime = signatureTime == null ? new Date() : signatureTime; mPassphrase = passphrase; + mCachePassphrase = true; } public CryptoInputParcel(Passphrase passphrase) { - mSignatureTime = new Date(); mPassphrase = passphrase; + mCachePassphrase = true; } public CryptoInputParcel(Date signatureTime) { + mHasSignature = true; mSignatureTime = signatureTime == null ? new Date() : signatureTime; mPassphrase = null; + mCachePassphrase = true; + } + + public CryptoInputParcel(ParcelableProxy parcelableProxy) { + this(); + mParcelableProxy = parcelableProxy; + } + + public CryptoInputParcel(Date signatureTime, boolean cachePassphrase) { + mHasSignature = true; + mSignatureTime = signatureTime == null ? new Date() : signatureTime; + mPassphrase = null; + mCachePassphrase = cachePassphrase; + } + + public CryptoInputParcel(boolean cachePassphrase) { + mCachePassphrase = cachePassphrase; } protected CryptoInputParcel(Parcel source) { - mSignatureTime = new Date(source.readLong()); + mHasSignature = source.readByte() != 0; + if (mHasSignature) { + mSignatureTime = new Date(source.readLong()); + } mPassphrase = source.readParcelable(getClass().getClassLoader()); + mParcelableProxy = source.readParcelable(getClass().getClassLoader()); + mCachePassphrase = source.readByte() != 0; { int count = source.readInt(); @@ -83,8 +118,13 @@ public class CryptoInputParcel implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeLong(mSignatureTime.getTime()); + dest.writeByte((byte) (mHasSignature ? 1 : 0)); + if (mHasSignature) { + dest.writeLong(mSignatureTime.getTime()); + } dest.writeParcelable(mPassphrase, 0); + dest.writeParcelable(mParcelableProxy, 0); + dest.writeByte((byte) (mCachePassphrase ? 1 : 0)); dest.writeInt(mCryptoData.size()); for (HashMap.Entry<ByteBuffer, byte[]> entry : mCryptoData.entrySet()) { @@ -93,12 +133,28 @@ public class CryptoInputParcel implements Parcelable { } } + public void addParcelableProxy(ParcelableProxy parcelableProxy) { + mParcelableProxy = parcelableProxy; + } + + public void addSignatureTime(Date signatureTime) { + mSignatureTime = signatureTime; + } + public void addCryptoData(byte[] hash, byte[] signedHash) { mCryptoData.put(ByteBuffer.wrap(hash), signedHash); } + public void addCryptoData(Map<ByteBuffer, byte[]> cachedSessionKeys) { + mCryptoData.putAll(cachedSessionKeys); + } + + public ParcelableProxy getParcelableProxy() { + return mParcelableProxy; + } + public Map<ByteBuffer, byte[]> getCryptoData() { - return Collections.unmodifiableMap(mCryptoData); + return mCryptoData; } public Date getSignatureTime() { @@ -138,4 +194,5 @@ public class CryptoInputParcel implements Parcelable { b.append("}"); return b.toString(); } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java index 535c1e735..e4dac3227 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java @@ -1,35 +1,37 @@ package org.sufficientlysecure.keychain.service.input; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; - import android.os.Parcel; import android.os.Parcelable; -import org.sufficientlysecure.keychain.Constants.key; +import org.sufficientlysecure.keychain.util.Passphrase; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; public class RequiredInputParcel implements Parcelable { public enum RequiredInputType { - PASSPHRASE, PASSPHRASE_SYMMETRIC, NFC_SIGN, NFC_DECRYPT + PASSPHRASE, PASSPHRASE_SYMMETRIC, NFC_SIGN, NFC_DECRYPT, NFC_MOVE_KEY_TO_CARD, ENABLE_ORBOT, + UPLOAD_FAIL_RETRY } public Date mSignatureTime; public final RequiredInputType mType; - public final byte[][] mInputHashes; + public final byte[][] mInputData; public final int[] mSignAlgos; private Long mMasterKeyId; private Long mSubKeyId; - private RequiredInputParcel(RequiredInputType type, byte[][] inputHashes, + private RequiredInputParcel(RequiredInputType type, byte[][] inputData, int[] signAlgos, Date signatureTime, Long masterKeyId, Long subKeyId) { mType = type; - mInputHashes = inputHashes; + mInputData = inputData; mSignAlgos = signAlgos; mSignatureTime = signatureTime; mMasterKeyId = masterKeyId; @@ -39,25 +41,25 @@ public class RequiredInputParcel implements Parcelable { public RequiredInputParcel(Parcel source) { mType = RequiredInputType.values()[source.readInt()]; - // 0 = none, 1 = both, 2 = only hashes (decrypt) - int hashTypes = source.readInt(); - if (hashTypes != 0) { + // 0 = none, 1 = signAlgos + inputData, 2 = only inputData (decrypt) + int inputDataType = source.readInt(); + if (inputDataType != 0) { int count = source.readInt(); - mInputHashes = new byte[count][]; - if (hashTypes == 1) { + mInputData = new byte[count][]; + if (inputDataType == 1) { mSignAlgos = new int[count]; for (int i = 0; i < count; i++) { - mInputHashes[i] = source.createByteArray(); + mInputData[i] = source.createByteArray(); mSignAlgos[i] = source.readInt(); } } else { mSignAlgos = null; for (int i = 0; i < count; i++) { - mInputHashes[i] = source.createByteArray(); + mInputData[i] = source.createByteArray(); } } } else { - mInputHashes = null; + mInputData = null; mSignAlgos = null; } @@ -75,16 +77,27 @@ public class RequiredInputParcel implements Parcelable { return mSubKeyId; } + public static RequiredInputParcel createRetryUploadOperation() { + return new RequiredInputParcel(RequiredInputType.UPLOAD_FAIL_RETRY, + null, null, null, 0L, 0L); + } + + public static RequiredInputParcel createOrbotRequiredOperation() { + return new RequiredInputParcel(RequiredInputType.ENABLE_ORBOT, null, null, null, 0L, 0L); + } + public static RequiredInputParcel createNfcSignOperation( + long masterKeyId, long subKeyId, byte[] inputHash, int signAlgo, Date signatureTime) { return new RequiredInputParcel(RequiredInputType.NFC_SIGN, new byte[][] { inputHash }, new int[] { signAlgo }, - signatureTime, null, null); + signatureTime, masterKeyId, subKeyId); } - public static RequiredInputParcel createNfcDecryptOperation(byte[] inputHash, long subKeyId) { + public static RequiredInputParcel createNfcDecryptOperation( + long masterKeyId, long subKeyId, byte[] encryptedSessionKey) { return new RequiredInputParcel(RequiredInputType.NFC_DECRYPT, - new byte[][] { inputHash }, null, null, null, subKeyId); + new byte[][] { encryptedSessionKey }, null, null, masterKeyId, subKeyId); } public static RequiredInputParcel createRequiredSignPassphrase( @@ -118,11 +131,11 @@ public class RequiredInputParcel implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mType.ordinal()); - if (mInputHashes != null) { + if (mInputData != null) { dest.writeInt(mSignAlgos != null ? 1 : 2); - dest.writeInt(mInputHashes.length); - for (int i = 0; i < mInputHashes.length; i++) { - dest.writeByteArray(mInputHashes[i]); + dest.writeInt(mInputData.length); + for (int i = 0; i < mInputData.length; i++) { + dest.writeByteArray(mInputData[i]); if (mSignAlgos != null) { dest.writeInt(mSignAlgos[i]); } @@ -165,10 +178,10 @@ public class RequiredInputParcel implements Parcelable { Date mSignatureTime; ArrayList<Integer> mSignAlgos = new ArrayList<>(); ArrayList<byte[]> mInputHashes = new ArrayList<>(); - Long mMasterKeyId; - Long mSubKeyId; + long mMasterKeyId; + long mSubKeyId; - public NfcSignOperationsBuilder(Date signatureTime, Long masterKeyId, Long subKeyId) { + public NfcSignOperationsBuilder(Date signatureTime, long masterKeyId, long subKeyId) { mSignatureTime = signatureTime; mMasterKeyId = masterKeyId; mSubKeyId = subKeyId; @@ -199,7 +212,7 @@ public class RequiredInputParcel implements Parcelable { throw new AssertionError("operation types must match, this is a progrmming error!"); } - Collections.addAll(mInputHashes, input.mInputHashes); + Collections.addAll(mInputHashes, input.mInputData); for (int signAlgo : input.mSignAlgos) { mSignAlgos.add(signAlgo); } @@ -211,4 +224,66 @@ public class RequiredInputParcel implements Parcelable { } + public static class NfcKeyToCardOperationsBuilder { + ArrayList<byte[]> mSubkeysToExport = new ArrayList<>(); + Long mMasterKeyId; + byte[] mPin; + byte[] mAdminPin; + + public NfcKeyToCardOperationsBuilder(Long masterKeyId) { + mMasterKeyId = masterKeyId; + } + + public RequiredInputParcel build() { + byte[][] inputData = new byte[mSubkeysToExport.size() + 2][]; + + // encode all subkeys into inputData + byte[][] subkeyData = new byte[mSubkeysToExport.size()][]; + mSubkeysToExport.toArray(subkeyData); + + // first two are PINs + inputData[0] = mPin; + inputData[1] = mAdminPin; + // then subkeys + System.arraycopy(subkeyData, 0, inputData, 2, subkeyData.length); + + ByteBuffer buf = ByteBuffer.wrap(mSubkeysToExport.get(0)); + + // We need to pass in a subkey here... + return new RequiredInputParcel(RequiredInputType.NFC_MOVE_KEY_TO_CARD, + inputData, null, null, mMasterKeyId, buf.getLong()); + } + + public void addSubkey(long subkeyId) { + byte[] subKeyId = new byte[8]; + ByteBuffer buf = ByteBuffer.wrap(subKeyId); + buf.putLong(subkeyId).rewind(); + mSubkeysToExport.add(subKeyId); + } + + public void setPin(Passphrase pin) { + mPin = pin.toStringUnsafe().getBytes(); + } + + public void setAdminPin(Passphrase adminPin) { + mAdminPin = adminPin.toStringUnsafe().getBytes(); + } + + public void addAll(RequiredInputParcel input) { + if (!mMasterKeyId.equals(input.mMasterKeyId)) { + throw new AssertionError("Master keys must match, this is a programming error!"); + } + if (input.mType != RequiredInputType.NFC_MOVE_KEY_TO_CARD) { + throw new AssertionError("Operation types must match, this is a programming error!"); + } + + Collections.addAll(mSubkeysToExport, input.mInputData); + } + + public boolean isEmpty() { + return mSubkeysToExport.isEmpty(); + } + + } + } |