aboutsummaryrefslogtreecommitdiffstats
path: root/OpenKeychain/src/main/java/org/sufficientlysecure
diff options
context:
space:
mode:
authorVincent Breitmoser <valodim@mugenguild.com>2015-04-24 14:18:01 +0200
committerVincent Breitmoser <valodim@mugenguild.com>2015-04-24 14:18:01 +0200
commitb4aec3114d9911cf9aef0d14ee697e5131b2853f (patch)
tree7237de5955ec34d8849737b2f9229bfbb37d0c45 /OpenKeychain/src/main/java/org/sufficientlysecure
parentc2163460971cc01e65d7dfd18eec491f01ebc744 (diff)
parentbaac30508d24dcda6135bf8ae338c99d8c3b8ad8 (diff)
downloadopen-keychain-b4aec3114d9911cf9aef0d14ee697e5131b2853f.tar.gz
open-keychain-b4aec3114d9911cf9aef0d14ee697e5131b2853f.tar.bz2
open-keychain-b4aec3114d9911cf9aef0d14ee697e5131b2853f.zip
Merge branch 'development' into linked-identities
Conflicts: Graphics/update-drawables.sh OpenKeychain-Test/src/test/java/org/sufficientlysecure/keychain/operations/CertifyOperationTest.java OpenKeychain/build.gradle OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure')
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java58
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java9
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java15
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java48
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/CertifyResult.java10
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java58
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/EditKeyResult.java9
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java63
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java120
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpEditKeyResult.java9
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpSignEncryptResult.java81
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PromoteKeyResult.java9
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java17
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java9
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java182
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java149
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java80
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java327
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java (renamed from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInput.java)129
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java121
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/SignEncryptParcel.java56
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/CryptoInputParcelCacheService.java246
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java355
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java4
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java4
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java62
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java134
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java6
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ServiceProgressHandler.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java141
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java214
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintActivity.java1
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java88
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java81
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java25
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java10
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiImportFragment.java261
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiWaitFragment.java58
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java203
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java37
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextActivity.java1
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java81
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java1
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java86
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java188
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivityInterface.java62
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java12
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java442
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java664
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java (renamed from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java)84
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeSymmetricFragment.java (renamed from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptSymmetricFragment.java)35
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java412
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java306
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java4
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java26
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java18
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayActivity.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcIntentActivity.java313
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java121
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java55
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeViewActivity.java1
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java1
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java28
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java1
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java1
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java1
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java180
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java1
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java236
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubiKeyFragment.java232
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SpacesItemDecoration.java93
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java (renamed from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BaseActivity.java)6
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java (renamed from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcActivity.java)465
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java131
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java21
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java12
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateFinalFragment.java11
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdViewFragment.java38
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java10
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/AlgorithmNames.java93
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java51
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/DatabaseUtil.java17
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FabContainer.java17
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeyUpdateHelper.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/NfcHelper.java9
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableCache.java94
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java8
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ProgressFixedScaler.java17
97 files changed, 4758 insertions, 3441 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java
index 4ceb34722..051517abd 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java
@@ -28,8 +28,9 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.Operat
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
-import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
+import org.sufficientlysecure.keychain.pgp.PgpCertifyOperation;
+import org.sufficientlysecure.keychain.pgp.PgpCertifyOperation.PgpCertifyResult;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
@@ -38,6 +39,9 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException
import org.sufficientlysecure.keychain.service.CertifyActionsParcel;
import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
import org.sufficientlysecure.keychain.service.ContactSyncAdapterService;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase;
@@ -60,7 +64,7 @@ public class CertifyOperation extends BaseOperation {
super(context, providerHelper, progressable, cancelled);
}
- public CertifyResult certify(CertifyActionsParcel parcel, String keyServerUri) {
+ public CertifyResult certify(CertifyActionsParcel parcel, CryptoInputParcel cryptoInput, String keyServerUri) {
OperationLog log = new OperationLog();
log.add(LogType.MSG_CRT, 0);
@@ -74,13 +78,14 @@ public class CertifyOperation extends BaseOperation {
mProviderHelper.getCanonicalizedSecretKeyRing(parcel.mMasterKeyId);
log.add(LogType.MSG_CRT_UNLOCK, 1);
certificationKey = secretKeyRing.getSecretKey();
- if (certificationKey.getSecretKeyType() == SecretKeyType.DIVERT_TO_CARD) {
- log.add(LogType.MSG_CRT_ERROR_DIVERT, 2);
- return new CertifyResult(CertifyResult.RESULT_ERROR, log);
+
+ if (!cryptoInput.hasPassphrase()) {
+ return new CertifyResult(log, RequiredInputParcel.createRequiredSignPassphrase(
+ certificationKey.getKeyId(), certificationKey.getKeyId(), null));
}
// certification is always with the master key id, so use that one
- Passphrase passphrase = getCachedPassphrase(parcel.mMasterKeyId, parcel.mMasterKeyId);
+ Passphrase passphrase = cryptoInput.getPassphrase();
if (!certificationKey.unlock(passphrase)) {
log.add(LogType.MSG_CRT_ERROR_UNLOCK, 2);
@@ -92,9 +97,6 @@ public class CertifyOperation extends BaseOperation {
} catch (NotFoundException e) {
log.add(LogType.MSG_CRT_ERROR_MASTER_NOT_FOUND, 2);
return new CertifyResult(CertifyResult.RESULT_ERROR, log);
- } catch (NoSecretKeyException e) {
- log.add(LogType.MSG_CRT_ERROR_MASTER_NOT_FOUND, 2);
- return new CertifyResult(CertifyResult.RESULT_ERROR, log);
}
ArrayList<UncachedKeyRing> certifiedKeys = new ArrayList<>();
@@ -103,6 +105,10 @@ public class CertifyOperation extends BaseOperation {
int certifyOk = 0, certifyError = 0, uploadOk = 0, uploadError = 0;
+ NfcSignOperationsBuilder allRequiredInput = new NfcSignOperationsBuilder(
+ cryptoInput.getSignatureTime(), certificationKey.getKeyId(),
+ certificationKey.getKeyId());
+
// Work through all requested certifications
for (CertifyAction action : parcel.mCertifyActions) {
@@ -123,28 +129,21 @@ public class CertifyOperation extends BaseOperation {
CanonicalizedPublicKeyRing publicRing =
mProviderHelper.getCanonicalizedPublicKeyRing(action.mMasterKeyId);
- UncachedKeyRing certifiedKey = null;
- if (action.mUserIds != null) {
- log.add(LogType.MSG_CRT_CERTIFY_UIDS, 2, action.mUserIds.size(),
- KeyFormattingUtils.convertKeyIdToHex(action.mMasterKeyId));
+ PgpCertifyOperation op = new PgpCertifyOperation();
+ PgpCertifyResult result = op.certify(certificationKey, publicRing,
+ log, 2, action, cryptoInput.getCryptoData(), cryptoInput.getSignatureTime());
- certifiedKey = certificationKey.certifyUserIds(
- publicRing, action.mUserIds, null, null);
+ if (!result.success()) {
+ certifyError += 1;
+ continue;
}
-
- if (action.mUserAttributes != null) {
- log.add(LogType.MSG_CRT_CERTIFY_UATS, 2, action.mUserAttributes.size(),
- KeyFormattingUtils.convertKeyIdToHex(action.mMasterKeyId));
-
- certifiedKey = certificationKey.certifyUserAttributes(
- publicRing, action.mUserAttributes, null, null);
+ if (result.nfcInputRequired()) {
+ RequiredInputParcel requiredInput = result.getRequiredInput();
+ allRequiredInput.addAll(requiredInput);
+ continue;
}
- if (certifiedKey == null) {
- certifyError += 1;
- log.add(LogType.MSG_CRT_WARN_CERT_FAILED, 3);
- }
- certifiedKeys.add(certifiedKey);
+ certifiedKeys.add(result.getCertifiedRing());
} catch (NotFoundException e) {
certifyError += 1;
@@ -153,6 +152,11 @@ public class CertifyOperation extends BaseOperation {
}
+ if ( ! allRequiredInput.isEmpty()) {
+ log.add(LogType.MSG_CRT_NFC_RETURN, 1);
+ return new CertifyResult(log, allRequiredInput.build());
+ }
+
log.add(LogType.MSG_CRT_SAVING, 1);
// Check if we were cancelled
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java
index a179b53ee..4072d91c5 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java
@@ -21,6 +21,7 @@ import android.content.Context;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
+import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult;
@@ -34,6 +35,7 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException
import org.sufficientlysecure.keychain.service.ContactSyncAdapterService;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.ProgressScaler;
@@ -56,7 +58,7 @@ public class EditKeyOperation extends BaseOperation {
super(context, providerHelper, progressable, cancelled);
}
- public EditKeyResult execute(SaveKeyringParcel saveParcel, Passphrase passphrase) {
+ public OperationResult execute(SaveKeyringParcel saveParcel, CryptoInputParcel cryptoInput) {
OperationLog log = new OperationLog();
log.add(LogType.MSG_ED, 0);
@@ -81,7 +83,10 @@ public class EditKeyOperation extends BaseOperation {
CanonicalizedSecretKeyRing secRing =
mProviderHelper.getCanonicalizedSecretKeyRing(saveParcel.mMasterKeyId);
- modifyResult = keyOperations.modifySecretKeyRing(secRing, saveParcel, passphrase);
+ modifyResult = keyOperations.modifySecretKeyRing(secRing, cryptoInput, saveParcel);
+ if (modifyResult.isPending()) {
+ return modifyResult;
+ }
} catch (NotFoundException e) {
log.add(LogType.MSG_ED_ERROR_KEY_NOT_FOUND, 2);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java
index f2516f1bd..ff0b545cd 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportExportOperation.java
@@ -230,7 +230,7 @@ public class ImportExportOperation extends BaseOperation {
}
} catch (Keyserver.QueryFailedException e) {
Log.e(Constants.TAG, "query failed", e);
- log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER_ERROR, 3);
+ log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER_ERROR, 3, e.getMessage());
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java
index fd86d4b92..ef08b0b77 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperation.java
@@ -50,7 +50,7 @@ public class PromoteKeyOperation extends BaseOperation {
super(context, providerHelper, progressable, cancelled);
}
- public PromoteKeyResult execute(long masterKeyId) {
+ public PromoteKeyResult execute(long masterKeyId, byte[] cardAid) {
OperationLog log = new OperationLog();
log.add(LogType.MSG_PR, 0);
@@ -58,27 +58,16 @@ public class PromoteKeyOperation extends BaseOperation {
// Perform actual type change
UncachedKeyRing promotedRing;
{
-
try {
- // This operation is only allowed for pure public keys
- // TODO delete secret keys if they are stripped, or have been moved to the card?
- if (mProviderHelper.getCachedPublicKeyRing(masterKeyId).hasAnySecret()) {
- log.add(LogType.MSG_PR_ERROR_ALREADY_SECRET, 2);
- return new PromoteKeyResult(PromoteKeyResult.RESULT_ERROR, log, null);
- }
-
log.add(LogType.MSG_PR_FETCHING, 1,
KeyFormattingUtils.convertKeyIdToHex(masterKeyId));
CanonicalizedPublicKeyRing pubRing =
mProviderHelper.getCanonicalizedPublicKeyRing(masterKeyId);
// create divert-to-card secret key from public key
- promotedRing = pubRing.createDummySecretRing();
+ promotedRing = pubRing.createDivertSecretRing(cardAid);
- } catch (PgpKeyNotFoundException e) {
- log.add(LogType.MSG_PR_ERROR_KEY_NOT_FOUND, 2);
- return new PromoteKeyResult(PromoteKeyResult.RESULT_ERROR, log, null);
} catch (NotFoundException e) {
log.add(LogType.MSG_PR_ERROR_KEY_NOT_FOUND, 2);
return new PromoteKeyResult(PromoteKeyResult.RESULT_ERROR, log, null);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java
index b5552a40d..651d15e8f 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java
@@ -20,14 +20,21 @@ package org.sufficientlysecure.keychain.operations;
import android.content.Context;
import android.net.Uri;
+import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
+import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
+import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.RequiredInputType;
import org.sufficientlysecure.keychain.util.FileHelper;
import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.ProgressScaler;
@@ -55,7 +62,7 @@ public class SignEncryptOperation extends BaseOperation {
super(context, providerHelper, progressable, cancelled);
}
- public SignEncryptResult execute(SignEncryptParcel input) {
+ public SignEncryptResult execute(SignEncryptParcel input, CryptoInputParcel cryptoInput) {
OperationLog log = new OperationLog();
log.add(LogType.MSG_SE, 0);
@@ -68,6 +75,21 @@ public class SignEncryptOperation extends BaseOperation {
int total = inputBytes != null ? 1 : inputUris.size(), count = 0;
ArrayList<PgpSignEncryptResult> results = new ArrayList<>();
+ NfcSignOperationsBuilder pendingInputBuilder = null;
+
+ // if signing subkey has not explicitly been set, get first usable subkey capable of signing
+ if (input.getSignatureMasterKeyId() != Constants.key.none
+ && input.getSignatureSubKeyId() == null) {
+ try {
+ long signKeyId = mProviderHelper.getCachedPublicKeyRing(
+ input.getSignatureMasterKeyId()).getSecretSignId();
+ input.setSignatureSubKeyId(signKeyId);
+ } catch (PgpKeyNotFoundException e) {
+ e.printStackTrace();
+ return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log, results);
+ }
+ }
+
do {
if (checkCancelled()) {
@@ -123,15 +145,22 @@ public class SignEncryptOperation extends BaseOperation {
PgpSignEncryptOperation op = new PgpSignEncryptOperation(mContext, mProviderHelper,
new ProgressScaler(mProgressable, 100 * count / total, 100 * ++count / total, 100), mCancelled);
- PgpSignEncryptResult result = op.execute(input, inputData, outStream);
+ PgpSignEncryptResult result = op.execute(input, cryptoInput, inputData, outStream);
results.add(result);
log.add(result, 2);
if (result.isPending()) {
- return new SignEncryptResult(SignEncryptResult.RESULT_PENDING, log, results);
- }
-
- if (!result.success()) {
+ RequiredInputParcel requiredInput = result.getRequiredInputParcel();
+ // Passphrase returns immediately, nfc are aggregated
+ if (requiredInput.mType == RequiredInputType.PASSPHRASE) {
+ return new SignEncryptResult(log, requiredInput, results);
+ }
+ if (pendingInputBuilder == null) {
+ pendingInputBuilder = new NfcSignOperationsBuilder(requiredInput.mSignatureTime,
+ input.getSignatureMasterKeyId(), input.getSignatureSubKeyId());
+ }
+ pendingInputBuilder.addAll(requiredInput);
+ } else if (!result.success()) {
return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log, results);
}
@@ -141,9 +170,12 @@ public class SignEncryptOperation extends BaseOperation {
} while (!inputUris.isEmpty());
+ if (pendingInputBuilder != null && !pendingInputBuilder.isEmpty()) {
+ return new SignEncryptResult(log, pendingInputBuilder.build(), results);
+ }
+
if (!outputUris.isEmpty()) {
- // Any output URIs left are indicative of a programming error
- log.add(LogType.MSG_SE_WARN_OUTPUT_LEFT, 1);
+ throw new AssertionError("Got outputs left but no inputs. This is a programming error, please report!");
}
log.add(LogType.MSG_SE_SUCCESS, 1);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/CertifyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/CertifyResult.java
index f56fe4bb9..0a0e63330 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/CertifyResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/CertifyResult.java
@@ -23,6 +23,7 @@ import android.content.Intent;
import android.os.Parcel;
import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.LogDisplayActivity;
import org.sufficientlysecure.keychain.ui.LogDisplayFragment;
import org.sufficientlysecure.keychain.ui.util.Notify;
@@ -30,16 +31,19 @@ import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener;
import org.sufficientlysecure.keychain.ui.util.Notify.Showable;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
-public class CertifyResult extends OperationResult {
-
+public class CertifyResult extends InputPendingResult {
int mCertifyOk, mCertifyError, mUploadOk, mUploadError;
public CertifyResult(int result, OperationLog log) {
super(result, log);
}
+ public CertifyResult(OperationLog log, RequiredInputParcel requiredInput) {
+ super(log, requiredInput);
+ }
+
public CertifyResult(int result, OperationLog log, int certifyOk, int certifyError, int uploadOk, int uploadError) {
- this(result, log);
+ super(result, log);
mCertifyOk = certifyOk;
mCertifyError = certifyError;
mUploadOk = uploadOk;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java
index 7df37cd9b..917b3415f 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java
@@ -22,23 +22,10 @@ import android.os.Parcel;
import org.openintents.openpgp.OpenPgpMetadata;
import org.openintents.openpgp.OpenPgpSignatureResult;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.util.Passphrase;
-public class DecryptVerifyResult extends OperationResult {
-
- // the fourth bit indicates a "data pending" result! (it's also a form of non-success)
- public static final int RESULT_PENDING = RESULT_ERROR + 8;
-
- // fifth to sixth bit in addition indicate specific type of pending
- public static final int RESULT_PENDING_ASYM_PASSPHRASE = RESULT_PENDING + 16;
- public static final int RESULT_PENDING_SYM_PASSPHRASE = RESULT_PENDING + 32;
- public static final int RESULT_PENDING_NFC = RESULT_PENDING + 64;
-
- long mKeyIdPassphraseNeeded;
-
- long mNfcSubKeyId;
- byte[] mNfcSessionKey;
- Passphrase mNfcPassphrase;
+public class DecryptVerifyResult extends InputPendingResult {
OpenPgpSignatureResult mSignatureResult;
OpenPgpMetadata mDecryptMetadata;
@@ -46,32 +33,6 @@ public class DecryptVerifyResult extends OperationResult {
// https://tools.ietf.org/html/rfc4880#page56
String mCharset;
- public long getKeyIdPassphraseNeeded() {
- return mKeyIdPassphraseNeeded;
- }
-
- public void setKeyIdPassphraseNeeded(long keyIdPassphraseNeeded) {
- mKeyIdPassphraseNeeded = keyIdPassphraseNeeded;
- }
-
- public void setNfcState(long subKeyId, byte[] sessionKey, Passphrase passphrase) {
- mNfcSubKeyId = subKeyId;
- mNfcSessionKey = sessionKey;
- mNfcPassphrase = passphrase;
- }
-
- public long getNfcSubKeyId() {
- return mNfcSubKeyId;
- }
-
- public byte[] getNfcEncryptedSessionKey() {
- return mNfcSessionKey;
- }
-
- public Passphrase getNfcPassphrase() {
- return mNfcPassphrase;
- }
-
public OpenPgpSignatureResult getSignatureResult() {
return mSignatureResult;
}
@@ -104,13 +65,14 @@ public class DecryptVerifyResult extends OperationResult {
super(result, log);
}
+ public DecryptVerifyResult(OperationLog log, RequiredInputParcel requiredInput) {
+ super(log, requiredInput);
+ }
+
public DecryptVerifyResult(Parcel source) {
super(source);
- mKeyIdPassphraseNeeded = source.readLong();
mSignatureResult = source.readParcelable(OpenPgpSignatureResult.class.getClassLoader());
mDecryptMetadata = source.readParcelable(OpenPgpMetadata.class.getClassLoader());
- mNfcSessionKey = source.readInt() != 0 ? source.createByteArray() : null;
- mNfcPassphrase = source.readParcelable(Passphrase.class.getClassLoader());
}
public int describeContents() {
@@ -119,16 +81,8 @@ public class DecryptVerifyResult extends OperationResult {
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
- dest.writeLong(mKeyIdPassphraseNeeded);
dest.writeParcelable(mSignatureResult, 0);
dest.writeParcelable(mDecryptMetadata, 0);
- if (mNfcSessionKey != null) {
- dest.writeInt(1);
- dest.writeByteArray(mNfcSessionKey);
- } else {
- dest.writeInt(0);
- }
- dest.writeParcelable(mNfcPassphrase, flags);
}
public static final Creator<DecryptVerifyResult> CREATOR = new Creator<DecryptVerifyResult>() {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/EditKeyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/EditKeyResult.java
index abcf575af..842b75c3b 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/EditKeyResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/EditKeyResult.java
@@ -31,13 +31,18 @@ public class EditKeyResult extends OperationResult {
public EditKeyResult(Parcel source) {
super(source);
- mMasterKeyId = source.readLong();
+ mMasterKeyId = source.readInt() != 0 ? source.readLong() : null;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
- dest.writeLong(mMasterKeyId);
+ if (mMasterKeyId != null) {
+ dest.writeInt(1);
+ dest.writeLong(mMasterKeyId);
+ } else {
+ dest.writeInt(0);
+ }
}
public static Creator<EditKeyResult> CREATOR = new Creator<EditKeyResult>() {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java
new file mode 100644
index 000000000..0b7aa6d03
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2015 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.operations.results;
+
+import java.util.ArrayList;
+
+import android.os.Parcel;
+
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
+
+public class InputPendingResult extends OperationResult {
+
+ // the fourth bit indicates a "data pending" result! (it's also a form of non-success)
+ public static final int RESULT_PENDING = RESULT_ERROR + 8;
+
+ final RequiredInputParcel mRequiredInput;
+
+ public InputPendingResult(int result, OperationLog log) {
+ super(result, log);
+ mRequiredInput = null;
+ }
+
+ public InputPendingResult(OperationLog log, RequiredInputParcel requiredInput) {
+ super(RESULT_PENDING, log);
+ mRequiredInput = requiredInput;
+ }
+
+ public InputPendingResult(Parcel source) {
+ super(source);
+ mRequiredInput = source.readParcelable(getClass().getClassLoader());
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeParcelable(mRequiredInput, 0);
+ }
+
+ public boolean isPending() {
+ return (mResult & RESULT_PENDING) == RESULT_PENDING;
+ }
+
+ public RequiredInputParcel getRequiredInputParcel() {
+ return mRequiredInput;
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java
index 3270d12d5..c93db5c39 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java
@@ -33,75 +33,33 @@ import org.sufficientlysecure.keychain.ui.util.Notify.Showable;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.ParcelableCache;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
-import java.util.UUID;
-import java.util.concurrent.ConcurrentHashMap;
-/** Represent the result of an operation.
+/**
+ * Represent the result of an operation.
*
* This class holds a result and the log of an operation. It can be subclassed
* to include typed additional information specific to the operation. To keep
* the class structure (somewhat) simple, this class contains an exhaustive
* list (ie, enum) of all possible log types, which should in all cases be tied
* to string resource ids.
- *
*/
public abstract class OperationResult implements Parcelable {
public static final String EXTRA_RESULT = "operation_result";
- public static final UUID NULL_UUID = new UUID(0,0);
/**
- * A HashMap of UUID:OperationLog which contains logs that we don't need
- * to care about. This is used such that when we become parceled, we are
- * well below the 1Mbit boundary that is specified.
+ * Instead of parceling the logs, they are cached to overcome the 1 MB boundary of
+ * Android's Binder. See ParcelableCache
*/
- private static ConcurrentHashMap<UUID, OperationLog> dehydratedLogs;
+ private static ParcelableCache<OperationLog> logCache;
static {
- // Static initializer for ConcurrentHashMap
- dehydratedLogs = new ConcurrentHashMap<UUID,OperationLog>();
- }
-
- /**
- * Dehydrate a log (such that it is available after deparcelization)
- *
- * Returns the NULL uuid (0) if you hand it null.
- * @param log An OperationLog to dehydrate
- * @return a UUID, the ticket for your dehydrated log
- *
- */
- private static UUID dehydrateLog(OperationLog log) {
- if(log == null) {
- return NULL_UUID;
- }
- else {
- UUID ticket = UUID.randomUUID();
- dehydratedLogs.put(ticket, log);
- return ticket;
- }
- }
-
- /***
- * Rehydrate a log after going through parcelization, invalidating its place in the
- * dehydration pool.
- * This is used such that when parcelized, the parcel is no larger than 1mbit.
- * @param ticket A UUID ticket that identifies the log in question.
- * @return An OperationLog.
- */
- private static OperationLog rehydrateLog(UUID ticket) {
- // UUID.equals isn't well documented; we use compareTo instead.
- if( NULL_UUID.compareTo(ticket) == 0 ) {
- return null;
- }
- else {
- OperationLog log = dehydratedLogs.get(ticket);
- dehydratedLogs.remove(ticket);
- return log;
- }
+ logCache = new ParcelableCache<>();
}
/** Holds the overall result, the number specifying varying degrees of success:
@@ -126,11 +84,8 @@ public abstract class OperationResult implements Parcelable {
public OperationResult(Parcel source) {
mResult = source.readInt();
- long mostSig = source.readLong();
- long leastSig = source.readLong();
- UUID mTicket = new UUID(mostSig, leastSig);
- // fetch the dehydrated log out of storage (this removes it from the dehydration pool)
- mLog = rehydrateLog(mTicket);
+ // get log out of cache based on UUID from source
+ mLog = logCache.readFromParcelAndGetFromCache(source);
}
public int getResult() {
@@ -250,12 +205,20 @@ public abstract class OperationResult implements Parcelable {
public Showable createNotify(final Activity activity) {
- Log.d(Constants.TAG, "mLog.getLast()"+mLog.getLast());
- Log.d(Constants.TAG, "mLog.getLast().mType"+mLog.getLast().mType);
- Log.d(Constants.TAG, "mLog.getLast().mType.getMsgId()"+mLog.getLast().mType.getMsgId());
-
// Take the last message as string
- int msgId = mLog.getLast().mType.getMsgId();
+ String logText;
+
+ LogEntryParcel entryParcel = mLog.getLast();
+ // special case: first parameter may be a quantity
+ if (entryParcel.mParameters != null && entryParcel.mParameters.length > 0
+ && entryParcel.mParameters[0] instanceof Integer) {
+ logText = activity.getResources().getQuantityString(entryParcel.mType.getMsgId(),
+ (Integer) entryParcel.mParameters[0],
+ entryParcel.mParameters);
+ } else {
+ logText = activity.getString(entryParcel.mType.getMsgId(),
+ entryParcel.mParameters);
+ }
Style style;
@@ -273,19 +236,19 @@ public abstract class OperationResult implements Parcelable {
}
if (getLog() == null || getLog().isEmpty()) {
- return Notify.create(activity, msgId, Notify.LENGTH_LONG, style);
+ return Notify.create(activity, logText, Notify.LENGTH_LONG, style);
}
- return Notify.create(activity, msgId, Notify.LENGTH_LONG, style,
- new ActionListener() {
- @Override
- public void onAction() {
- Intent intent = new Intent(
- activity, LogDisplayActivity.class);
- intent.putExtra(LogDisplayFragment.EXTRA_RESULT, OperationResult.this);
- activity.startActivity(intent);
- }
- }, R.string.view_log);
+ return Notify.create(activity, logText, Notify.LENGTH_LONG, style,
+ new ActionListener() {
+ @Override
+ public void onAction() {
+ Intent intent = new Intent(
+ activity, LogDisplayActivity.class);
+ intent.putExtra(LogDisplayFragment.EXTRA_RESULT, OperationResult.this);
+ activity.startActivity(intent);
+ }
+ }, R.string.view_log);
}
@@ -512,6 +475,7 @@ public abstract class OperationResult implements Parcelable {
// secret key modify
MSG_MF (LogLevel.START, R.string.msg_mr),
+ MSG_MF_DIVERT (LogLevel.DEBUG, R.string.msg_mf_divert),
MSG_MF_ERROR_DIVERT_SERIAL (LogLevel.ERROR, R.string.msg_mf_error_divert_serial),
MSG_MF_ERROR_ENCODE (LogLevel.ERROR, R.string.msg_mf_error_encode),
MSG_MF_ERROR_FINGERPRINT (LogLevel.ERROR, R.string.msg_mf_error_fingerprint),
@@ -521,6 +485,7 @@ public abstract class OperationResult implements Parcelable {
MSG_MF_ERROR_NO_CERTIFY (LogLevel.ERROR, R.string.msg_cr_error_no_certify),
MSG_MF_ERROR_NOEXIST_PRIMARY (LogLevel.ERROR, R.string.msg_mf_error_noexist_primary),
MSG_MF_ERROR_NOEXIST_REVOKE (LogLevel.ERROR, R.string.msg_mf_error_noexist_revoke),
+ MSG_MF_ERROR_NOOP (LogLevel.ERROR, R.string.msg_mf_error_noop),
MSG_MF_ERROR_NULL_EXPIRY (LogLevel.ERROR, R.string.msg_mf_error_null_expiry),
MSG_MF_ERROR_PASSPHRASE_MASTER(LogLevel.ERROR, R.string.msg_mf_error_passphrase_master),
MSG_MF_ERROR_PAST_EXPIRY(LogLevel.ERROR, R.string.msg_mf_error_past_expiry),
@@ -538,6 +503,9 @@ public abstract class OperationResult implements Parcelable {
MSG_MF_PASSPHRASE_FAIL (LogLevel.WARN, R.string.msg_mf_passphrase_fail),
MSG_MF_PRIMARY_REPLACE_OLD (LogLevel.DEBUG, R.string.msg_mf_primary_replace_old),
MSG_MF_PRIMARY_NEW (LogLevel.DEBUG, R.string.msg_mf_primary_new),
+ MSG_MF_RESTRICTED_MODE (LogLevel.INFO, R.string.msg_mf_restricted_mode),
+ MSG_MF_REQUIRE_DIVERT (LogLevel.OK, R.string.msg_mf_require_divert),
+ MSG_MF_REQUIRE_PASSPHRASE (LogLevel.OK, R.string.msg_mf_require_passphrase),
MSG_MF_SUBKEY_CHANGE (LogLevel.INFO, R.string.msg_mf_subkey_change),
MSG_MF_SUBKEY_NEW_ID (LogLevel.DEBUG, R.string.msg_mf_subkey_new_id),
MSG_MF_SUBKEY_NEW (LogLevel.INFO, R.string.msg_mf_subkey_new),
@@ -590,13 +558,11 @@ public abstract class OperationResult implements Parcelable {
// promote key
MSG_PR (LogLevel.START, R.string.msg_pr),
- MSG_PR_ERROR_ALREADY_SECRET (LogLevel.ERROR, R.string.msg_pr_error_already_secret),
MSG_PR_ERROR_KEY_NOT_FOUND (LogLevel.ERROR, R.string.msg_pr_error_key_not_found),
MSG_PR_FETCHING (LogLevel.DEBUG, R.string.msg_pr_fetching),
MSG_PR_SUCCESS (LogLevel.OK, R.string.msg_pr_success),
// messages used in UI code
- MSG_EK_ERROR_DIVERT (LogLevel.ERROR, R.string.msg_ek_error_divert),
MSG_EK_ERROR_DUMMY (LogLevel.ERROR, R.string.msg_ek_error_dummy),
MSG_EK_ERROR_NOT_FOUND (LogLevel.ERROR, R.string.msg_ek_error_not_found),
@@ -660,7 +626,6 @@ public abstract class OperationResult implements Parcelable {
MSG_SE_ERROR_INPUT_URI_NOT_FOUND (LogLevel.ERROR, R.string.msg_se_error_input_uri_not_found),
MSG_SE_ERROR_OUTPUT_URI_NOT_FOUND (LogLevel.ERROR, R.string.msg_se_error_output_uri_not_found),
MSG_SE_ERROR_TOO_MANY_INPUTS (LogLevel.ERROR, R.string.msg_se_error_too_many_inputs),
- MSG_SE_WARN_OUTPUT_LEFT (LogLevel.WARN, R.string.msg_se_warn_output_left),
MSG_SE_SUCCESS (LogLevel.OK, R.string.msg_se_success),
// pgpsignencrypt
@@ -697,9 +662,9 @@ public abstract class OperationResult implements Parcelable {
MSG_CRT_ERROR_MASTER_NOT_FOUND (LogLevel.ERROR, R.string.msg_crt_error_master_not_found),
MSG_CRT_ERROR_NOTHING (LogLevel.ERROR, R.string.msg_crt_error_nothing),
MSG_CRT_ERROR_UNLOCK (LogLevel.ERROR, R.string.msg_crt_error_unlock),
- MSG_CRT_ERROR_DIVERT (LogLevel.ERROR, R.string.msg_crt_error_divert),
MSG_CRT (LogLevel.START, R.string.msg_crt),
MSG_CRT_MASTER_FETCH (LogLevel.DEBUG, R.string.msg_crt_master_fetch),
+ MSG_CRT_NFC_RETURN (LogLevel.OK, R.string.msg_crt_nfc_return),
MSG_CRT_SAVE (LogLevel.DEBUG, R.string.msg_crt_save),
MSG_CRT_SAVING (LogLevel.DEBUG, R.string.msg_crt_saving),
MSG_CRT_SUCCESS (LogLevel.OK, R.string.msg_crt_success),
@@ -823,11 +788,8 @@ public abstract class OperationResult implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mResult);
- // Get a ticket for our log.
- UUID mTicket = dehydrateLog(mLog);
- // And write out the UUID most and least significant bits.
- dest.writeLong(mTicket.getMostSignificantBits());
- dest.writeLong(mTicket.getLeastSignificantBits());
+ // cache log and write UUID to dest
+ logCache.cacheAndWriteToParcel(mLog, dest);
}
public static class OperationLog implements Iterable<LogEntryParcel> {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpEditKeyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpEditKeyResult.java
index 611353ac9..38edbf6ee 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpEditKeyResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpEditKeyResult.java
@@ -22,8 +22,10 @@ import android.os.Parcel;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
-public class PgpEditKeyResult extends OperationResult {
+
+public class PgpEditKeyResult extends InputPendingResult {
private transient UncachedKeyRing mRing;
public final long mRingMasterKeyId;
@@ -35,6 +37,11 @@ public class PgpEditKeyResult extends OperationResult {
mRingMasterKeyId = ring != null ? ring.getMasterKeyId() : Constants.key.none;
}
+ public PgpEditKeyResult(OperationLog log, RequiredInputParcel requiredInput) {
+ super(log, requiredInput);
+ mRingMasterKeyId = Constants.key.none;
+ }
+
public UncachedKeyRing getRing() {
return mRing;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpSignEncryptResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpSignEncryptResult.java
index cf40001b3..acb265462 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpSignEncryptResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpSignEncryptResult.java
@@ -19,85 +19,31 @@ package org.sufficientlysecure.keychain.operations.results;
import android.os.Parcel;
-import org.sufficientlysecure.keychain.util.Passphrase;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
-import java.util.Date;
-public class PgpSignEncryptResult extends OperationResult {
+public class PgpSignEncryptResult extends InputPendingResult {
- // the fourth bit indicates a "data pending" result! (it's also a form of non-success)
- public static final int RESULT_PENDING = RESULT_ERROR + 8;
-
- // fifth to sixth bit in addition indicate specific type of pending
- public static final int RESULT_PENDING_PASSPHRASE = RESULT_PENDING + 16;
- public static final int RESULT_PENDING_NFC = RESULT_PENDING + 32;
-
- long mKeyIdPassphraseNeeded;
-
- long mNfcKeyId;
- byte[] mNfcHash;
- int mNfcAlgo;
- Date mNfcTimestamp;
- Passphrase mNfcPassphrase;
byte[] mDetachedSignature;
- public long getKeyIdPassphraseNeeded() {
- return mKeyIdPassphraseNeeded;
- }
-
- public void setKeyIdPassphraseNeeded(long keyIdPassphraseNeeded) {
- mKeyIdPassphraseNeeded = keyIdPassphraseNeeded;
- }
-
- public void setNfcData(long nfcKeyId, byte[] nfcHash, int nfcAlgo, Date nfcTimestamp, Passphrase passphrase) {
- mNfcKeyId = nfcKeyId;
- mNfcHash = nfcHash;
- mNfcAlgo = nfcAlgo;
- mNfcTimestamp = nfcTimestamp;
- mNfcPassphrase = passphrase;
- }
-
public void setDetachedSignature(byte[] detachedSignature) {
mDetachedSignature = detachedSignature;
}
- public long getNfcKeyId() {
- return mNfcKeyId;
- }
-
- public byte[] getNfcHash() {
- return mNfcHash;
- }
-
- public int getNfcAlgo() {
- return mNfcAlgo;
- }
-
- public Date getNfcTimestamp() {
- return mNfcTimestamp;
- }
-
- public Passphrase getNfcPassphrase() {
- return mNfcPassphrase;
- }
-
public byte[] getDetachedSignature() {
return mDetachedSignature;
}
- public boolean isPending() {
- return (mResult & RESULT_PENDING) == RESULT_PENDING;
- }
-
public PgpSignEncryptResult(int result, OperationLog log) {
super(result, log);
}
+ public PgpSignEncryptResult(OperationLog log, RequiredInputParcel requiredInput) {
+ super(log, requiredInput);
+ }
+
public PgpSignEncryptResult(Parcel source) {
super(source);
- mNfcHash = source.readInt() != 0 ? source.createByteArray() : null;
- mNfcAlgo = source.readInt();
- mNfcTimestamp = source.readInt() != 0 ? new Date(source.readLong()) : null;
mDetachedSignature = source.readInt() != 0 ? source.createByteArray() : null;
}
@@ -107,19 +53,6 @@ public class PgpSignEncryptResult extends OperationResult {
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
- if (mNfcHash != null) {
- dest.writeInt(1);
- dest.writeByteArray(mNfcHash);
- } else {
- dest.writeInt(0);
- }
- dest.writeInt(mNfcAlgo);
- if (mNfcTimestamp != null) {
- dest.writeInt(1);
- dest.writeLong(mNfcTimestamp.getTime());
- } else {
- dest.writeInt(0);
- }
if (mDetachedSignature != null) {
dest.writeInt(1);
dest.writeByteArray(mDetachedSignature);
@@ -138,4 +71,4 @@ public class PgpSignEncryptResult extends OperationResult {
}
};
-} \ No newline at end of file
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PromoteKeyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PromoteKeyResult.java
index af9aff84a..d6c7a1ee0 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PromoteKeyResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PromoteKeyResult.java
@@ -31,13 +31,18 @@ public class PromoteKeyResult extends OperationResult {
public PromoteKeyResult(Parcel source) {
super(source);
- mMasterKeyId = source.readLong();
+ mMasterKeyId = source.readInt() != 0 ? source.readLong() : null;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
- dest.writeLong(mMasterKeyId);
+ if (mMasterKeyId != null) {
+ dest.writeInt(1);
+ dest.writeLong(mMasterKeyId);
+ } else {
+ dest.writeInt(0);
+ }
}
public static Creator<PromoteKeyResult> CREATOR = new Creator<PromoteKeyResult>() {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java
index ed0de65b0..b05921b0d 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java
@@ -21,20 +21,17 @@ import android.os.Parcel;
import java.util.ArrayList;
-public class SignEncryptResult extends OperationResult {
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
+
+
+public class SignEncryptResult extends InputPendingResult {
ArrayList<PgpSignEncryptResult> mResults;
byte[] mResultBytes;
- public static final int RESULT_PENDING = RESULT_ERROR + 8;
-
- public PgpSignEncryptResult getPending() {
- for (PgpSignEncryptResult sub : mResults) {
- if (sub.isPending()) {
- return sub;
- }
- }
- return null;
+ public SignEncryptResult(OperationLog log, RequiredInputParcel requiredInput, ArrayList<PgpSignEncryptResult> results) {
+ super(log, requiredInput);
+ mResults = results;
}
public SignEncryptResult(int result, OperationLog log, ArrayList<PgpSignEncryptResult> results) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java
index c2506685d..8432b8f9f 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java
@@ -98,11 +98,14 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing {
/** Create a dummy secret ring from this key */
public UncachedKeyRing createDummySecretRing () {
-
- PGPSecretKeyRing secRing = PGPSecretKeyRing.constructDummyFromPublic(getRing(),
- S2K.GNU_PROTECTION_MODE_NO_PRIVATE_KEY);
+ PGPSecretKeyRing secRing = PGPSecretKeyRing.constructDummyFromPublic(getRing(), null);
return new UncachedKeyRing(secRing);
+ }
+ /** Create a dummy secret ring from this key */
+ public UncachedKeyRing createDivertSecretRing (byte[] cardAid) {
+ PGPSecretKeyRing secRing = PGPSecretKeyRing.constructDummyFromPublic(getRing(), cardAid);
+ return new UncachedKeyRing(secRing);
}
} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java
index 6ce77394c..39d0a2f1d 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java
@@ -40,13 +40,17 @@ import org.spongycastle.openpgp.operator.jcajce.NfcSyncPublicKeyDataDecryptorFac
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase;
+import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+
/**
* Wrapper for a PGPSecretKey.
@@ -184,13 +188,13 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
return PgpConstants.sPreferredHashAlgorithms;
}
- private PGPContentSignerBuilder getContentSignerBuilder(int hashAlgo, byte[] nfcSignedHash,
- Date nfcCreationTimestamp) {
+ private PGPContentSignerBuilder getContentSignerBuilder(int hashAlgo,
+ Map<ByteBuffer,byte[]> signedHashes) {
if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) {
// use synchronous "NFC based" SignerBuilder
return new NfcSyncPGPContentSignerBuilder(
mSecretKey.getPublicKey().getAlgorithm(), hashAlgo,
- mSecretKey.getKeyID(), nfcSignedHash, nfcCreationTimestamp)
+ mSecretKey.getKeyID(), signedHashes)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
} else {
// content signer based on signing key algorithm and chosen hash algorithm
@@ -200,29 +204,43 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
}
}
- public PGPSignatureGenerator getSignatureGenerator(int hashAlgo, boolean cleartext,
- byte[] nfcSignedHash, Date nfcCreationTimestamp)
- throws PgpGeneralException {
+ public PGPSignatureGenerator getCertSignatureGenerator(Map<ByteBuffer, byte[]> signedHashes) {
+ PGPContentSignerBuilder contentSignerBuilder = getContentSignerBuilder(
+ PgpConstants.CERTIFY_HASH_ALGO, signedHashes);
+
if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {
throw new PrivateKeyNotUnlockedException();
}
- if (nfcSignedHash != null && nfcCreationTimestamp == null) {
- throw new PgpGeneralException("Got nfc hash without timestamp!!");
+
+ PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
+ try {
+ signatureGenerator.init(PGPSignature.DEFAULT_CERTIFICATION, mPrivateKey);
+ return signatureGenerator;
+ } catch (PGPException e) {
+ Log.e(Constants.TAG, "signing error", e);
+ return null;
+ }
+ }
+
+ public PGPSignatureGenerator getDataSignatureGenerator(int hashAlgo, boolean cleartext,
+ Map<ByteBuffer, byte[]> signedHashes, Date creationTimestamp)
+ throws PgpGeneralException {
+ if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {
+ throw new PrivateKeyNotUnlockedException();
}
// We explicitly create a signature creation timestamp in this place.
// That way, we can inject an artificial one from outside, ie the one
// used in previous runs of this function.
- if (nfcCreationTimestamp == null) {
+ if (creationTimestamp == null) {
// to sign using nfc PgpSignEncrypt is executed two times.
// the first time it stops to return the PendingIntent for nfc connection and signing the hash
// the second time the signed hash is used.
// to get the same hash we cache the timestamp for the second round!
- nfcCreationTimestamp = new Date();
+ creationTimestamp = new Date();
}
- PGPContentSignerBuilder contentSignerBuilder = getContentSignerBuilder(hashAlgo,
- nfcSignedHash, nfcCreationTimestamp);
+ PGPContentSignerBuilder contentSignerBuilder = getContentSignerBuilder(hashAlgo, signedHashes);
int signatureType;
if (cleartext) {
@@ -238,7 +256,7 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
spGen.setSignerUserID(false, mRing.getPrimaryUserIdWithFallback());
- spGen.setSignatureCreationTime(false, nfcCreationTimestamp);
+ spGen.setSignatureCreationTime(false, creationTimestamp);
signatureGenerator.setHashedSubpackets(spGen.generate());
return signatureGenerator;
} catch (PgpKeyNotFoundException | PGPException e) {
@@ -247,145 +265,24 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
}
}
- public PublicKeyDataDecryptorFactory getDecryptorFactory(byte[] nfcDecryptedSessionKey) {
+ public PublicKeyDataDecryptorFactory getDecryptorFactory(CryptoInputParcel cryptoInput) {
if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {
throw new PrivateKeyNotUnlockedException();
}
if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) {
return new NfcSyncPublicKeyDataDecryptorFactoryBuilder()
- .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(nfcDecryptedSessionKey);
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
+ cryptoInput.getCryptoData()
+ );
} else {
return new JcePublicKeyDataDecryptorFactoryBuilder()
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(mPrivateKey);
}
}
- /**
- * Certify the given pubkeyid with the given masterkeyid.
- *
- * @param publicKeyRing Keyring to add certification to.
- * @param userIds User IDs to certify
- * @return A keyring with added certifications
- */
- public UncachedKeyRing certifyUserIds(CanonicalizedPublicKeyRing publicKeyRing, List<String> userIds,
- byte[] nfcSignedHash, Date nfcCreationTimestamp) {
- if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {
- throw new PrivateKeyNotUnlockedException();
- }
- if (!isMasterKey()) {
- throw new AssertionError("tried to certify with non-master key, this is a programming error!");
- }
- if (publicKeyRing.getMasterKeyId() == getKeyId()) {
- throw new AssertionError("key tried to self-certify, this is a programming error!");
- }
-
- // create a signatureGenerator from the supplied masterKeyId and passphrase
- PGPSignatureGenerator signatureGenerator;
- {
- PGPContentSignerBuilder contentSignerBuilder = getContentSignerBuilder(
- PgpConstants.CERTIFY_HASH_ALGO, nfcSignedHash, nfcCreationTimestamp);
-
- signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
- try {
- signatureGenerator.init(PGPSignature.DEFAULT_CERTIFICATION, mPrivateKey);
- } catch (PGPException e) {
- Log.e(Constants.TAG, "signing error", e);
- return null;
- }
- }
-
- { // supply signatureGenerator with a SubpacketVector
- PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
- if (nfcCreationTimestamp != null) {
- spGen.setSignatureCreationTime(false, nfcCreationTimestamp);
- Log.d(Constants.TAG, "For NFC: set sig creation time to " + nfcCreationTimestamp);
- }
- PGPSignatureSubpacketVector packetVector = spGen.generate();
- signatureGenerator.setHashedSubpackets(packetVector);
- }
-
- // get the master subkey (which we certify for)
- PGPPublicKey publicKey = publicKeyRing.getPublicKey().getPublicKey();
-
- // fetch public key ring, add the certification and return it
- try {
- for (String userId : userIds) {
- PGPSignature sig = signatureGenerator.generateCertification(userId, publicKey);
- publicKey = PGPPublicKey.addCertification(publicKey, userId, sig);
- }
- } catch (PGPException e) {
- Log.e(Constants.TAG, "signing error", e);
- return null;
- }
-
- PGPPublicKeyRing ring = PGPPublicKeyRing.insertPublicKey(publicKeyRing.getRing(), publicKey);
-
- return new UncachedKeyRing(ring);
- }
-
- /**
- * Certify the given user attributes with the given masterkeyid.
- *
- * @param publicKeyRing Keyring to add certification to.
- * @param userAttributes User IDs to certify, or all if null
- * @return A keyring with added certifications
- */
- public UncachedKeyRing certifyUserAttributes(CanonicalizedPublicKeyRing publicKeyRing,
- List<WrappedUserAttribute> userAttributes, byte[] nfcSignedHash, Date nfcCreationTimestamp) {
- if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {
- throw new PrivateKeyNotUnlockedException();
- }
- if (!isMasterKey()) {
- throw new AssertionError("tried to certify with non-master key, this is a programming error!");
- }
- if (publicKeyRing.getMasterKeyId() == getKeyId()) {
- throw new AssertionError("key tried to self-certify, this is a programming error!");
- }
-
- // create a signatureGenerator from the supplied masterKeyId and passphrase
- PGPSignatureGenerator signatureGenerator;
- {
- PGPContentSignerBuilder contentSignerBuilder = getContentSignerBuilder(
- PgpConstants.CERTIFY_HASH_ALGO, nfcSignedHash, nfcCreationTimestamp);
-
- signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
- try {
- signatureGenerator.init(PGPSignature.DEFAULT_CERTIFICATION, mPrivateKey);
- } catch (PGPException e) {
- Log.e(Constants.TAG, "signing error", e);
- return null;
- }
- }
-
- { // supply signatureGenerator with a SubpacketVector
- PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
- if (nfcCreationTimestamp != null) {
- spGen.setSignatureCreationTime(false, nfcCreationTimestamp);
- Log.d(Constants.TAG, "For NFC: set sig creation time to " + nfcCreationTimestamp);
- }
- PGPSignatureSubpacketVector packetVector = spGen.generate();
- signatureGenerator.setHashedSubpackets(packetVector);
- }
-
- // get the master subkey (which we certify for)
- PGPPublicKey publicKey = publicKeyRing.getPublicKey().getPublicKey();
-
- // fetch public key ring, add the certification and return it
- try {
- for (WrappedUserAttribute userAttribute : userAttributes) {
- PGPUserAttributeSubpacketVector vector = userAttribute.getVector();
- PGPSignature sig = signatureGenerator.generateCertification(vector, publicKey);
- publicKey = PGPPublicKey.addCertification(publicKey, vector, sig);
- }
- } catch (PGPException e) {
- Log.e(Constants.TAG, "signing error", e);
- return null;
- }
-
- PGPPublicKeyRing ring = PGPPublicKeyRing.insertPublicKey(publicKeyRing.getRing(), publicKey);
-
- return new UncachedKeyRing(ring);
+ public byte[] getIv() {
+ return mSecretKey.getIV();
}
static class PrivateKeyNotUnlockedException extends RuntimeException {
@@ -402,4 +299,9 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
return mPrivateKey;
}
+ // HACK, for TESTING ONLY!!
+ PGPSecretKey getSecretKey() {
+ return mSecretKey;
+ }
+
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java
new file mode 100644
index 000000000..90ec3053f
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpCertifyOperation.java
@@ -0,0 +1,149 @@
+package org.sufficientlysecure.keychain.pgp;
+
+
+import java.nio.ByteBuffer;
+import java.util.Date;
+import java.util.Map;
+
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPPublicKey;
+import org.spongycastle.openpgp.PGPPublicKeyRing;
+import org.spongycastle.openpgp.PGPSignature;
+import org.spongycastle.openpgp.PGPSignatureGenerator;
+import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator;
+import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
+import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector;
+import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
+import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
+import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder;
+import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
+import org.sufficientlysecure.keychain.util.Log;
+
+
+public class PgpCertifyOperation {
+
+ public PgpCertifyResult certify(
+ CanonicalizedSecretKey secretKey,
+ CanonicalizedPublicKeyRing publicRing,
+ OperationLog log,
+ int indent,
+ CertifyAction action,
+ Map<ByteBuffer,byte[]> signedHashes,
+ Date creationTimestamp) {
+
+ if (!secretKey.isMasterKey()) {
+ throw new AssertionError("tried to certify with non-master key, this is a programming error!");
+ }
+ if (publicRing.getMasterKeyId() == secretKey.getKeyId()) {
+ throw new AssertionError("key tried to self-certify, this is a programming error!");
+ }
+
+ // create a signatureGenerator from the supplied masterKeyId and passphrase
+ PGPSignatureGenerator signatureGenerator = secretKey.getCertSignatureGenerator(signedHashes);
+
+ { // supply signatureGenerator with a SubpacketVector
+ PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
+ if (creationTimestamp != null) {
+ spGen.setSignatureCreationTime(false, creationTimestamp);
+ Log.d(Constants.TAG, "For NFC: set sig creation time to " + creationTimestamp);
+ }
+ PGPSignatureSubpacketVector packetVector = spGen.generate();
+ signatureGenerator.setHashedSubpackets(packetVector);
+ }
+
+ // get the master subkey (which we certify for)
+ PGPPublicKey publicKey = publicRing.getPublicKey().getPublicKey();
+
+ NfcSignOperationsBuilder requiredInput = new NfcSignOperationsBuilder(creationTimestamp,
+ publicKey.getKeyID(), publicKey.getKeyID());
+
+ try {
+ if (action.mUserIds != null) {
+ log.add(LogType.MSG_CRT_CERTIFY_UIDS, 2, action.mUserIds.size(),
+ KeyFormattingUtils.convertKeyIdToHex(action.mMasterKeyId));
+
+ // fetch public key ring, add the certification and return it
+ for (String userId : action.mUserIds) {
+ try {
+ PGPSignature sig = signatureGenerator.generateCertification(userId, publicKey);
+ publicKey = PGPPublicKey.addCertification(publicKey, userId, sig);
+ } catch (NfcInteractionNeeded e) {
+ requiredInput.addHash(e.hashToSign, e.hashAlgo);
+ }
+ }
+
+ }
+
+ if (action.mUserAttributes != null) {
+ log.add(LogType.MSG_CRT_CERTIFY_UATS, 2, action.mUserAttributes.size(),
+ KeyFormattingUtils.convertKeyIdToHex(action.mMasterKeyId));
+
+ // fetch public key ring, add the certification and return it
+ for (WrappedUserAttribute userAttribute : action.mUserAttributes) {
+ PGPUserAttributeSubpacketVector vector = userAttribute.getVector();
+ try {
+ PGPSignature sig = signatureGenerator.generateCertification(vector, publicKey);
+ publicKey = PGPPublicKey.addCertification(publicKey, vector, sig);
+ } catch (NfcInteractionNeeded e) {
+ requiredInput.addHash(e.hashToSign, e.hashAlgo);
+ }
+ }
+
+ }
+ } catch (PGPException e) {
+ Log.e(Constants.TAG, "signing error", e);
+ return new PgpCertifyResult();
+ }
+
+ if (!requiredInput.isEmpty()) {
+ return new PgpCertifyResult(requiredInput.build());
+ }
+
+ PGPPublicKeyRing ring = PGPPublicKeyRing.insertPublicKey(publicRing.getRing(), publicKey);
+ return new PgpCertifyResult(new UncachedKeyRing(ring));
+
+ }
+
+ public static class PgpCertifyResult {
+
+ final RequiredInputParcel mRequiredInput;
+ final UncachedKeyRing mCertifiedRing;
+
+ PgpCertifyResult() {
+ mRequiredInput = null;
+ mCertifiedRing = null;
+ }
+
+ PgpCertifyResult(RequiredInputParcel requiredInput) {
+ mRequiredInput = requiredInput;
+ mCertifiedRing = null;
+ }
+
+ PgpCertifyResult(UncachedKeyRing certifiedRing) {
+ mRequiredInput = null;
+ mCertifiedRing = certifiedRing;
+ }
+
+ public boolean success() {
+ return mCertifiedRing != null || mRequiredInput != null;
+ }
+
+ public boolean nfcInputRequired() {
+ return mRequiredInput != null;
+ }
+
+ public UncachedKeyRing getCertifiedRing() {
+ return mCertifiedRing;
+ }
+
+ public RequiredInputParcel getRequiredInput() {
+ return mRequiredInput;
+ }
+
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java
index 364a1067d..f6580b85a 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java
@@ -47,16 +47,15 @@ import org.spongycastle.openpgp.operator.jcajce.NfcSyncPublicKeyDataDecryptorFac
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.BaseOperation;
+import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
-import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
-import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
-import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
-import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log;
@@ -84,10 +83,8 @@ public class PgpDecryptVerify extends BaseOperation {
private OutputStream mOutStream;
private boolean mAllowSymmetricDecryption;
- private Passphrase mPassphrase;
private Set<Long> mAllowedKeyIds;
private boolean mDecryptMetadataOnly;
- private byte[] mDecryptedSessionKey;
private byte[] mDetachedSignature;
private String mRequiredSignerFingerprint;
private boolean mSignedLiteralData;
@@ -100,10 +97,8 @@ public class PgpDecryptVerify extends BaseOperation {
this.mOutStream = builder.mOutStream;
this.mAllowSymmetricDecryption = builder.mAllowSymmetricDecryption;
- this.mPassphrase = builder.mPassphrase;
this.mAllowedKeyIds = builder.mAllowedKeyIds;
this.mDecryptMetadataOnly = builder.mDecryptMetadataOnly;
- this.mDecryptedSessionKey = builder.mDecryptedSessionKey;
this.mDetachedSignature = builder.mDetachedSignature;
this.mSignedLiteralData = builder.mSignedLiteralData;
this.mRequiredSignerFingerprint = builder.mRequiredSignerFingerprint;
@@ -119,10 +114,8 @@ public class PgpDecryptVerify extends BaseOperation {
private OutputStream mOutStream = null;
private Progressable mProgressable = null;
private boolean mAllowSymmetricDecryption = true;
- private Passphrase mPassphrase = null;
private Set<Long> mAllowedKeyIds = null;
private boolean mDecryptMetadataOnly = false;
- private byte[] mDecryptedSessionKey = null;
private byte[] mDetachedSignature = null;
private String mRequiredSignerFingerprint = null;
private boolean mSignedLiteralData = false;
@@ -160,11 +153,6 @@ public class PgpDecryptVerify extends BaseOperation {
return this;
}
- public Builder setPassphrase(Passphrase passphrase) {
- mPassphrase = passphrase;
- return this;
- }
-
/**
* Allow these key ids alone for decryption.
* This means only ciphertexts encrypted for one of these private key can be decrypted.
@@ -183,11 +171,6 @@ public class PgpDecryptVerify extends BaseOperation {
return this;
}
- public Builder setNfcState(byte[] decryptedSessionKey) {
- mDecryptedSessionKey = decryptedSessionKey;
- return this;
- }
-
/**
* If detachedSignature != null, it will be used exclusively to verify the signature
*/
@@ -204,7 +187,7 @@ public class PgpDecryptVerify extends BaseOperation {
/**
* Decrypts and/or verifies data based on parameters of class
*/
- public DecryptVerifyResult execute() {
+ public DecryptVerifyResult execute(CryptoInputParcel cryptoInput) {
try {
if (mDetachedSignature != null) {
Log.d(Constants.TAG, "Detached signature present, verifying with this signature only");
@@ -226,10 +209,10 @@ public class PgpDecryptVerify extends BaseOperation {
return verifyCleartextSignature(aIn, 0);
} else {
// else: ascii armored encryption! go on...
- return decryptVerify(in, 0);
+ return decryptVerify(cryptoInput, in, 0);
}
} else {
- return decryptVerify(in, 0);
+ return decryptVerify(cryptoInput, in, 0);
}
}
} catch (PGPException e) {
@@ -248,7 +231,8 @@ public class PgpDecryptVerify extends BaseOperation {
/**
* Verify Keybase.io style signed literal data
*/
- private DecryptVerifyResult verifySignedLiteralData(InputStream in, int indent) throws IOException, PGPException {
+ private DecryptVerifyResult verifySignedLiteralData(InputStream in, int indent)
+ throws IOException, PGPException {
OperationLog log = new OperationLog();
log.add(LogType.MSG_VL, indent);
@@ -378,7 +362,8 @@ public class PgpDecryptVerify extends BaseOperation {
/**
* Decrypt and/or verifies binary or ascii armored pgp
*/
- private DecryptVerifyResult decryptVerify(InputStream in, int indent) throws IOException, PGPException {
+ private DecryptVerifyResult decryptVerify(CryptoInputParcel cryptoInput,
+ InputStream in, int indent) throws IOException, PGPException {
OperationLog log = new OperationLog();
@@ -433,6 +418,8 @@ public class PgpDecryptVerify extends BaseOperation {
}
}
+ Passphrase passphrase = null;
+
// go through all objects and find one we can decrypt
while (it.hasNext()) {
Object obj = it.next();
@@ -492,11 +479,15 @@ public class PgpDecryptVerify extends BaseOperation {
encryptedDataAsymmetric = encData;
- // if no passphrase was explicitly set try to get it from the cache service
- if (mPassphrase == null) {
+ if (secretEncryptionKey.getSecretKeyType() == SecretKeyType.DIVERT_TO_CARD) {
+ passphrase = null;
+ } else if (cryptoInput.hasPassphrase()) {
+ passphrase = cryptoInput.getPassphrase();
+ } else {
+ // if no passphrase was explicitly set try to get it from the cache service
try {
// returns "" if key has no passphrase
- mPassphrase = getCachedPassphrase(subKeyId);
+ passphrase = getCachedPassphrase(subKeyId);
log.add(LogType.MSG_DC_PASS_CACHED, indent + 1);
} catch (PassphraseCacheInterface.NoSecretKeyException e) {
log.add(LogType.MSG_DC_ERROR_NO_KEY, indent + 1);
@@ -504,12 +495,11 @@ public class PgpDecryptVerify extends BaseOperation {
}
// if passphrase was not cached, return here indicating that a passphrase is missing!
- if (mPassphrase == null) {
+ if (passphrase == null) {
log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1);
- DecryptVerifyResult result =
- new DecryptVerifyResult(DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE, log);
- result.setKeyIdPassphraseNeeded(subKeyId);
- return result;
+ return new DecryptVerifyResult(log,
+ RequiredInputParcel.createRequiredDecryptPassphrase(
+ secretKeyRing.getMasterKeyId(), secretEncryptionKey.getKeyId()));
}
}
@@ -536,11 +526,14 @@ public class PgpDecryptVerify extends BaseOperation {
// if no passphrase is given, return here
// indicating that a passphrase is missing!
- if (mPassphrase == null) {
+ if (!cryptoInput.hasPassphrase()) {
log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1);
- return new DecryptVerifyResult(DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE, log);
+ return new DecryptVerifyResult(log,
+ RequiredInputParcel.createRequiredSymmetricPassphrase());
}
+ passphrase = cryptoInput.getPassphrase();
+
// break out of while, only decrypt the first packet
break;
}
@@ -573,7 +566,7 @@ public class PgpDecryptVerify extends BaseOperation {
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build();
PBEDataDecryptorFactory decryptorFactory = new JcePBEDataDecryptorFactoryBuilder(
digestCalcProvider).setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
- mPassphrase.getCharArray());
+ passphrase.getCharArray());
clear = encryptedDataSymmetric.getDataStream(decryptorFactory);
encryptedData = encryptedDataSymmetric;
@@ -585,7 +578,7 @@ public class PgpDecryptVerify extends BaseOperation {
try {
log.add(LogType.MSG_DC_UNLOCKING, indent + 1);
- if (!secretEncryptionKey.unlock(mPassphrase)) {
+ if (!secretEncryptionKey.unlock(passphrase)) {
log.add(LogType.MSG_DC_ERROR_BAD_PASSPHRASE, indent + 1);
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
}
@@ -599,16 +592,15 @@ public class PgpDecryptVerify extends BaseOperation {
try {
PublicKeyDataDecryptorFactory decryptorFactory
- = secretEncryptionKey.getDecryptorFactory(mDecryptedSessionKey);
+ = secretEncryptionKey.getDecryptorFactory(cryptoInput);
clear = encryptedDataAsymmetric.getDataStream(decryptorFactory);
symmetricEncryptionAlgo = encryptedDataAsymmetric.getSymmetricAlgorithm(decryptorFactory);
} catch (NfcSyncPublicKeyDataDecryptorFactoryBuilder.NfcInteractionNeeded e) {
log.add(LogType.MSG_DC_PENDING_NFC, indent + 1);
- DecryptVerifyResult result =
- new DecryptVerifyResult(DecryptVerifyResult.RESULT_PENDING_NFC, log);
- result.setNfcState(secretEncryptionKey.getKeyId(), e.encryptedSessionKey, mPassphrase);
- return result;
+ return new DecryptVerifyResult(log, RequiredInputParcel.createNfcDecryptOperation(
+ e.encryptedSessionKey, secretEncryptionKey.getKeyId()
+ ));
}
encryptedData = encryptedDataAsymmetric;
} else {
@@ -878,8 +870,8 @@ public class PgpDecryptVerify extends BaseOperation {
* The method is heavily based on
* pg/src/main/java/org/spongycastle/openpgp/examples/ClearSignedFileProcessor.java
*/
- private DecryptVerifyResult verifyCleartextSignature(ArmoredInputStream aIn, int indent)
- throws IOException, PGPException {
+ private DecryptVerifyResult verifyCleartextSignature(
+ ArmoredInputStream aIn, int indent) throws IOException, PGPException {
OperationLog log = new OperationLog();
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java
index b3bf92364..89db378a9 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java
@@ -18,7 +18,7 @@
package org.sufficientlysecure.keychain.pgp;
-import org.spongycastle.bcpg.HashAlgorithmTags;
+import org.spongycastle.bcpg.S2K;
import org.spongycastle.bcpg.sig.Features;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.jce.spec.ElGamalParameterSpec;
@@ -43,6 +43,8 @@ import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBu
import org.spongycastle.openpgp.operator.jcajce.JcaPGPKeyPair;
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
+import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder;
+import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
@@ -54,6 +56,9 @@ import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
@@ -86,6 +91,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
* This indicator may be null.
*/
public class PgpKeyOperation {
+
private Stack<Progressable> mProgress;
private AtomicBoolean mCancelled;
@@ -317,7 +323,8 @@ public class PgpKeyOperation {
masterSecretKey.getEncoded(), new JcaKeyFingerprintCalculator());
subProgressPush(50, 100);
- return internal(sKR, masterSecretKey, add.mFlags, add.mExpiry, saveParcel, new Passphrase(), log);
+ CryptoInputParcel cryptoInput = new CryptoInputParcel(new Date(), new Passphrase(""));
+ return internal(sKR, masterSecretKey, add.mFlags, add.mExpiry, cryptoInput, saveParcel, log);
} catch (PGPException e) {
log.add(LogType.MSG_CR_ERROR_INTERNAL_PGP, indent);
@@ -348,8 +355,9 @@ public class PgpKeyOperation {
* namely stripping of subkeys and changing the protection mode of dummy keys.
*
*/
- public PgpEditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR, SaveKeyringParcel saveParcel,
- Passphrase passphrase) {
+ public PgpEditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR,
+ CryptoInputParcel cryptoInput,
+ SaveKeyringParcel saveParcel) {
OperationLog log = new OperationLog();
int indent = 0;
@@ -387,11 +395,24 @@ public class PgpKeyOperation {
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
- // If we have no passphrase, only allow restricted operation
- if (passphrase == null) {
+ if (saveParcel.isEmpty()) {
+ log.add(LogType.MSG_MF_ERROR_NOOP, indent);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
+ }
+
+ if (isDummy(masterSecretKey) || saveParcel.isRestrictedOnly()) {
+ log.add(LogType.MSG_MF_RESTRICTED_MODE, indent);
return internalRestricted(sKR, saveParcel, log);
}
+ // Do we require a passphrase? If so, pass it along
+ if (!isDivertToCard(masterSecretKey) && !cryptoInput.hasPassphrase()) {
+ log.add(LogType.MSG_MF_REQUIRE_PASSPHRASE, indent);
+ return new PgpEditKeyResult(log, RequiredInputParcel.createRequiredSignPassphrase(
+ masterSecretKey.getKeyID(), masterSecretKey.getKeyID(),
+ cryptoInput.getSignatureTime()));
+ }
+
// read masterKeyFlags, and use the same as before.
// since this is the master key, this contains at least CERTIFY_OTHER
PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey();
@@ -399,33 +420,45 @@ public class PgpKeyOperation {
Date expiryTime = wsKR.getPublicKey().getExpiryTime();
long masterKeyExpiry = expiryTime != null ? expiryTime.getTime() / 1000 : 0L;
- return internal(sKR, masterSecretKey, masterKeyFlags, masterKeyExpiry, saveParcel, passphrase, log);
+ return internal(sKR, masterSecretKey, masterKeyFlags, masterKeyExpiry, cryptoInput, saveParcel, log);
}
private PgpEditKeyResult internal(PGPSecretKeyRing sKR, PGPSecretKey masterSecretKey,
int masterKeyFlags, long masterKeyExpiry,
- SaveKeyringParcel saveParcel, Passphrase passphrase,
+ CryptoInputParcel cryptoInput,
+ SaveKeyringParcel saveParcel,
OperationLog log) {
int indent = 1;
+ NfcSignOperationsBuilder nfcSignOps = new NfcSignOperationsBuilder(
+ cryptoInput.getSignatureTime(), masterSecretKey.getKeyID(),
+ masterSecretKey.getKeyID());
+
progress(R.string.progress_modify, 0);
PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey();
- // 1. Unlock private key
- progress(R.string.progress_modify_unlock, 10);
- log.add(LogType.MSG_MF_UNLOCK, indent);
PGPPrivateKey masterPrivateKey;
- {
- try {
- PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
- Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.getCharArray());
- masterPrivateKey = masterSecretKey.extractPrivateKey(keyDecryptor);
- } catch (PGPException e) {
- log.add(LogType.MSG_MF_UNLOCK_ERROR, indent + 1);
- return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
+
+ if (isDivertToCard(masterSecretKey)) {
+ masterPrivateKey = null;
+ log.add(LogType.MSG_MF_DIVERT, indent);
+ } else {
+
+ // 1. Unlock private key
+ progress(R.string.progress_modify_unlock, 10);
+ log.add(LogType.MSG_MF_UNLOCK, indent);
+ {
+ try {
+ PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
+ Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(cryptoInput.getPassphrase().getCharArray());
+ masterPrivateKey = masterSecretKey.extractPrivateKey(keyDecryptor);
+ } catch (PGPException e) {
+ log.add(LogType.MSG_MF_UNLOCK_ERROR, indent + 1);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
+ }
}
}
@@ -449,7 +482,7 @@ public class PgpKeyOperation {
String userId = saveParcel.mAddUserIds.get(i);
log.add(LogType.MSG_MF_UID_ADD, indent, userId);
- if (userId.equals("")) {
+ if ("".equals(userId)) {
log.add(LogType.MSG_MF_UID_ERROR_EMPTY, indent + 1);
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
@@ -480,9 +513,16 @@ public class PgpKeyOperation {
boolean isPrimary = saveParcel.mChangePrimaryUserId != null
&& userId.equals(saveParcel.mChangePrimaryUserId);
// generate and add new certificate
- PGPSignature cert = generateUserIdSignature(masterPrivateKey,
- masterPublicKey, userId, isPrimary, masterKeyFlags, masterKeyExpiry);
- modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert);
+ try {
+ PGPSignature cert = generateUserIdSignature(
+ getSignatureGenerator(masterSecretKey, cryptoInput),
+ cryptoInput.getSignatureTime(),
+ masterPrivateKey, masterPublicKey, userId,
+ isPrimary, masterKeyFlags, masterKeyExpiry);
+ modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert);
+ } catch (NfcInteractionNeeded e) {
+ nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
+ }
}
subProgressPop();
@@ -509,9 +549,15 @@ public class PgpKeyOperation {
PGPUserAttributeSubpacketVector vector = attribute.getVector();
// generate and add new certificate
- PGPSignature cert = generateUserAttributeSignature(masterPrivateKey,
- masterPublicKey, vector);
- modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, vector, cert);
+ try {
+ PGPSignature cert = generateUserAttributeSignature(
+ getSignatureGenerator(masterSecretKey, cryptoInput),
+ cryptoInput.getSignatureTime(),
+ masterPrivateKey, masterPublicKey, vector);
+ modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, vector, cert);
+ } catch (NfcInteractionNeeded e) {
+ nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
+ }
}
subProgressPop();
@@ -539,9 +585,15 @@ public class PgpKeyOperation {
// a duplicate revocation will be removed during canonicalization, so no need to
// take care of that here.
- PGPSignature cert = generateRevocationSignature(masterPrivateKey,
- masterPublicKey, userId);
- modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert);
+ try {
+ PGPSignature cert = generateRevocationSignature(
+ getSignatureGenerator(masterSecretKey, cryptoInput),
+ cryptoInput.getSignatureTime(),
+ masterPrivateKey, masterPublicKey, userId);
+ modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, userId, cert);
+ } catch (NfcInteractionNeeded e) {
+ nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
+ }
}
subProgressPop();
@@ -611,11 +663,18 @@ public class PgpKeyOperation {
log.add(LogType.MSG_MF_PRIMARY_REPLACE_OLD, indent);
modifiedPublicKey = PGPPublicKey.removeCertification(
modifiedPublicKey, userId, currentCert);
- PGPSignature newCert = generateUserIdSignature(
- masterPrivateKey, masterPublicKey, userId, false,
- masterKeyFlags, masterKeyExpiry);
- modifiedPublicKey = PGPPublicKey.addCertification(
- modifiedPublicKey, userId, newCert);
+ try {
+ PGPSignature newCert = generateUserIdSignature(
+ getSignatureGenerator(masterSecretKey, cryptoInput),
+ cryptoInput.getSignatureTime(),
+ masterPrivateKey, masterPublicKey, userId, false,
+ masterKeyFlags, masterKeyExpiry);
+ modifiedPublicKey = PGPPublicKey.addCertification(
+ modifiedPublicKey, userId, newCert);
+ } catch (NfcInteractionNeeded e) {
+ nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
+ }
+
continue;
}
@@ -627,11 +686,17 @@ public class PgpKeyOperation {
log.add(LogType.MSG_MF_PRIMARY_NEW, indent);
modifiedPublicKey = PGPPublicKey.removeCertification(
modifiedPublicKey, userId, currentCert);
- PGPSignature newCert = generateUserIdSignature(
- masterPrivateKey, masterPublicKey, userId, true,
- masterKeyFlags, masterKeyExpiry);
- modifiedPublicKey = PGPPublicKey.addCertification(
- modifiedPublicKey, userId, newCert);
+ try {
+ PGPSignature newCert = generateUserIdSignature(
+ getSignatureGenerator(masterSecretKey, cryptoInput),
+ cryptoInput.getSignatureTime(),
+ masterPrivateKey, masterPublicKey, userId, true,
+ masterKeyFlags, masterKeyExpiry);
+ modifiedPublicKey = PGPPublicKey.addCertification(
+ modifiedPublicKey, userId, newCert);
+ } catch (NfcInteractionNeeded e) {
+ nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
+ }
ok = true;
}
@@ -718,8 +783,9 @@ public class PgpKeyOperation {
}
PGPPublicKey pKey =
- updateMasterCertificates(masterPrivateKey, masterPublicKey,
- flags, expiry, indent, log);
+ updateMasterCertificates(
+ masterSecretKey, masterPrivateKey, masterPublicKey,
+ flags, expiry, cryptoInput, nfcSignOps, indent, log);
if (pKey == null) {
// error log entry has already been added by updateMasterCertificates itself
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
@@ -756,9 +822,16 @@ public class PgpKeyOperation {
pKey = PGPPublicKey.removeCertification(pKey, sig);
}
+ PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
+ cryptoInput.getPassphrase().getCharArray());
+ PGPPrivateKey subPrivateKey = sKey.extractPrivateKey(keyDecryptor);
+ PGPSignature sig = generateSubkeyBindingSignature(
+ getSignatureGenerator(masterSecretKey, cryptoInput),
+ cryptoInput.getSignatureTime(),
+ masterPublicKey, masterPrivateKey, subPrivateKey, pKey, flags, expiry);
+
// generate and add new signature
- PGPSignature sig = generateSubkeyBindingSignature(masterPublicKey, masterPrivateKey,
- sKey, pKey, flags, expiry, passphrase);
pKey = PGPPublicKey.addCertification(pKey, sig);
sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey));
}
@@ -782,10 +855,17 @@ public class PgpKeyOperation {
PGPPublicKey pKey = sKey.getPublicKey();
// generate and add new signature
- PGPSignature sig = generateRevocationSignature(masterPublicKey, masterPrivateKey, pKey);
-
- pKey = PGPPublicKey.addCertification(pKey, sig);
- sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey));
+ try {
+ PGPSignature sig = generateRevocationSignature(
+ getSignatureGenerator(masterSecretKey, cryptoInput),
+ cryptoInput.getSignatureTime(),
+ masterPublicKey, masterPrivateKey, pKey);
+
+ pKey = PGPPublicKey.addCertification(pKey, sig);
+ sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey));
+ } catch (NfcInteractionNeeded e) {
+ nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
+ }
}
subProgressPop();
@@ -828,10 +908,16 @@ public class PgpKeyOperation {
// add subkey binding signature (making this a sub rather than master key)
PGPPublicKey pKey = keyPair.getPublicKey();
- PGPSignature cert = generateSubkeyBindingSignature(
- masterPublicKey, masterPrivateKey, keyPair.getPrivateKey(), pKey,
- add.mFlags, add.mExpiry);
- pKey = PGPPublicKey.addSubkeyBindingCertification(pKey, cert);
+ try {
+ PGPSignature cert = generateSubkeyBindingSignature(
+ getSignatureGenerator(masterSecretKey, cryptoInput),
+ cryptoInput.getSignatureTime(),
+ masterPublicKey, masterPrivateKey, keyPair.getPrivateKey(), pKey,
+ add.mFlags, add.mExpiry);
+ pKey = PGPPublicKey.addSubkeyBindingCertification(pKey, cert);
+ } catch (NfcInteractionNeeded e) {
+ nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
+ }
PGPSecretKey sKey; {
// Build key encrypter and decrypter based on passphrase
@@ -840,7 +926,8 @@ public class PgpKeyOperation {
PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
PgpConstants.SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc,
PgpConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT)
- .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.getCharArray());
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
+ cryptoInput.getPassphrase().getCharArray());
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder()
.build().get(PgpConstants.SECRET_KEY_SIGNATURE_CHECKSUM_HASH_ALGO);
@@ -868,7 +955,7 @@ public class PgpKeyOperation {
indent += 1;
sKR = applyNewUnlock(sKR, masterPublicKey, masterPrivateKey,
- passphrase, saveParcel.mNewUnlock, log, indent);
+ cryptoInput.getPassphrase(), saveParcel.mNewUnlock, log, indent);
if (sKR == null) {
// The error has been logged above, just return a bad state
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
@@ -892,6 +979,12 @@ public class PgpKeyOperation {
}
progress(R.string.progress_done, 100);
+
+ if (!nfcSignOps.isEmpty()) {
+ log.add(LogType.MSG_MF_REQUIRE_DIVERT, indent);
+ return new PgpEditKeyResult(log, nfcSignOps.build());
+ }
+
log.add(LogType.MSG_MF_SUCCESS, indent);
return new PgpEditKeyResult(OperationResult.RESULT_OK, log, new UncachedKeyRing(sKR));
@@ -1064,8 +1157,7 @@ public class PgpKeyOperation {
PBESecretKeyEncryptor keyEncryptorNew = new JcePBESecretKeyEncryptorBuilder(
PgpConstants.SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc,
PgpConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT)
- .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
- newPassphrase.getCharArray());
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(newPassphrase.getCharArray());
// noinspection unchecked
for (PGPSecretKey sKey : new IterableIterator<PGPSecretKey>(sKR.getSecretKeys())) {
@@ -1116,9 +1208,13 @@ public class PgpKeyOperation {
}
/** Update all (non-revoked) uid signatures with new flags and expiry time. */
- private static PGPPublicKey updateMasterCertificates(
- PGPPrivateKey masterPrivateKey, PGPPublicKey masterPublicKey,
- int flags, long expiry, int indent, OperationLog log)
+ private PGPPublicKey updateMasterCertificates(
+ PGPSecretKey masterSecretKey, PGPPrivateKey masterPrivateKey,
+ PGPPublicKey masterPublicKey,
+ int flags, long expiry,
+ CryptoInputParcel cryptoInput,
+ NfcSignOperationsBuilder nfcSignOps,
+ int indent, OperationLog log)
throws PGPException, IOException, SignatureException {
// keep track if we actually changed one
@@ -1173,10 +1269,16 @@ public class PgpKeyOperation {
currentCert.getHashedSubPackets().isPrimaryUserID();
modifiedPublicKey = PGPPublicKey.removeCertification(
modifiedPublicKey, userId, currentCert);
- PGPSignature newCert = generateUserIdSignature(
- masterPrivateKey, masterPublicKey, userId, isPrimary, flags, expiry);
- modifiedPublicKey = PGPPublicKey.addCertification(
- modifiedPublicKey, userId, newCert);
+ try {
+ PGPSignature newCert = generateUserIdSignature(
+ getSignatureGenerator(masterSecretKey, cryptoInput),
+ cryptoInput.getSignatureTime(),
+ masterPrivateKey, masterPublicKey, userId, isPrimary, flags, expiry);
+ modifiedPublicKey = PGPPublicKey.addCertification(
+ modifiedPublicKey, userId, newCert);
+ } catch (NfcInteractionNeeded e) {
+ nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
+ }
ok = true;
}
@@ -1191,15 +1293,37 @@ public class PgpKeyOperation {
}
- private static PGPSignature generateUserIdSignature(
+ static PGPSignatureGenerator getSignatureGenerator(
+ PGPSecretKey secretKey, CryptoInputParcel cryptoInput) {
+
+ PGPContentSignerBuilder builder;
+
+ S2K s2k = secretKey.getS2K();
+ if (s2k != null && s2k.getType() == S2K.GNU_DUMMY_S2K
+ && s2k.getProtectionMode() == S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD) {
+ // use synchronous "NFC based" SignerBuilder
+ builder = new NfcSyncPGPContentSignerBuilder(
+ secretKey.getPublicKey().getAlgorithm(),
+ PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO,
+ secretKey.getKeyID(), cryptoInput.getCryptoData())
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+ } else {
+ // content signer based on signing key algorithm and chosen hash algorithm
+ builder = new JcaPGPContentSignerBuilder(
+ secretKey.getPublicKey().getAlgorithm(),
+ PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO)
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+ }
+
+ return new PGPSignatureGenerator(builder);
+
+ }
+
+ private PGPSignature generateUserIdSignature(
+ PGPSignatureGenerator sGen, Date creationTime,
PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId, boolean primary,
int flags, long expiry)
throws IOException, PGPException, SignatureException {
- PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
- masterPrivateKey.getPublicKeyPacket().getAlgorithm(),
- PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO)
- .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
- PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
{
@@ -1223,7 +1347,7 @@ public class PgpKeyOperation {
hashedPacketsGen.setPrimaryUserID(false, primary);
/* critical subpackets: we consider those important for a modern pgp implementation */
- hashedPacketsGen.setSignatureCreationTime(true, new Date());
+ hashedPacketsGen.setSignatureCreationTime(true, creationTime);
// Request that senders add the MDC to the message (authenticate unsigned messages)
hashedPacketsGen.setFeature(true, Features.FEATURE_MODIFICATION_DETECTION);
hashedPacketsGen.setKeyFlags(true, flags);
@@ -1239,19 +1363,15 @@ public class PgpKeyOperation {
}
private static PGPSignature generateUserAttributeSignature(
+ PGPSignatureGenerator sGen, Date creationTime,
PGPPrivateKey masterPrivateKey, PGPPublicKey pKey,
PGPUserAttributeSubpacketVector vector)
throws IOException, PGPException, SignatureException {
- PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
- masterPrivateKey.getPublicKeyPacket().getAlgorithm(),
- PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO)
- .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
- PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
{
/* critical subpackets: we consider those important for a modern pgp implementation */
- hashedPacketsGen.setSignatureCreationTime(true, new Date());
+ hashedPacketsGen.setSignatureCreationTime(true, creationTime);
}
sGen.setHashedSubpackets(hashedPacketsGen.generate());
@@ -1260,29 +1380,24 @@ public class PgpKeyOperation {
}
private static PGPSignature generateRevocationSignature(
+ PGPSignatureGenerator sGen, Date creationTime,
PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId)
+
throws IOException, PGPException, SignatureException {
- PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
- masterPrivateKey.getPublicKeyPacket().getAlgorithm(),
- PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO)
- .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
- PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
- subHashedPacketsGen.setSignatureCreationTime(true, new Date());
+ subHashedPacketsGen.setSignatureCreationTime(true, creationTime);
sGen.setHashedSubpackets(subHashedPacketsGen.generate());
sGen.init(PGPSignature.CERTIFICATION_REVOCATION, masterPrivateKey);
return sGen.generateCertification(userId, pKey);
}
private static PGPSignature generateRevocationSignature(
+ PGPSignatureGenerator sGen, Date creationTime,
PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey, PGPPublicKey pKey)
throws IOException, PGPException, SignatureException {
- PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
- masterPublicKey.getAlgorithm(), PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO)
- .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
- PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
+
PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
- subHashedPacketsGen.setSignatureCreationTime(true, new Date());
+ subHashedPacketsGen.setSignatureCreationTime(true, creationTime);
sGen.setHashedSubpackets(subHashedPacketsGen.generate());
// Generate key revocation or subkey revocation, depending on master/subkey-ness
if (masterPublicKey.getKeyID() == pKey.getKeyID()) {
@@ -1294,26 +1409,12 @@ public class PgpKeyOperation {
}
}
- private static PGPSignature generateSubkeyBindingSignature(
- PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey,
- PGPSecretKey sKey, PGPPublicKey pKey, int flags, long expiry, Passphrase passphrase)
- throws IOException, PGPException, SignatureException {
- PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
- .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
- passphrase.getCharArray());
- PGPPrivateKey subPrivateKey = sKey.extractPrivateKey(keyDecryptor);
- return generateSubkeyBindingSignature(masterPublicKey, masterPrivateKey, subPrivateKey,
- pKey, flags, expiry);
- }
-
static PGPSignature generateSubkeyBindingSignature(
+ PGPSignatureGenerator sGen, Date creationTime,
PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey,
PGPPrivateKey subPrivateKey, PGPPublicKey pKey, int flags, long expiry)
throws IOException, PGPException, SignatureException {
- // date for signing
- Date creationTime = new Date();
-
PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
// If this key can sign, we need a primary key binding signature
@@ -1324,10 +1425,10 @@ public class PgpKeyOperation {
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
pKey.getAlgorithm(), PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
- PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
- sGen.init(PGPSignature.PRIMARYKEY_BINDING, subPrivateKey);
- sGen.setHashedSubpackets(subHashedPacketsGen.generate());
- PGPSignature certification = sGen.generateCertification(masterPublicKey, pKey);
+ PGPSignatureGenerator subSigGen = new PGPSignatureGenerator(signerBuilder);
+ subSigGen.init(PGPSignature.PRIMARYKEY_BINDING, subPrivateKey);
+ subSigGen.setHashedSubpackets(subHashedPacketsGen.generate());
+ PGPSignature certification = subSigGen.generateCertification(masterPublicKey, pKey);
unhashedPacketsGen.setEmbeddedSignature(true, certification);
}
@@ -1342,10 +1443,6 @@ public class PgpKeyOperation {
}
}
- PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
- masterPublicKey.getAlgorithm(), PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO)
- .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
- PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
sGen.init(PGPSignature.SUBKEY_BINDING, masterPrivateKey);
sGen.setHashedSubpackets(hashedPacketsGen.generate());
sGen.setUnhashedSubpackets(unhashedPacketsGen.generate());
@@ -1372,4 +1469,16 @@ public class PgpKeyOperation {
return flags;
}
+ private static boolean isDummy(PGPSecretKey secretKey) {
+ S2K s2k = secretKey.getS2K();
+ return s2k.getType() == S2K.GNU_DUMMY_S2K
+ && s2k.getProtectionMode() == S2K.GNU_PROTECTION_MODE_NO_PRIVATE_KEY;
+ }
+
+ private static boolean isDivertToCard(PGPSecretKey secretKey) {
+ S2K s2k = secretKey.getS2K();
+ return s2k.getType() == S2K.GNU_DUMMY_S2K
+ && s2k.getProtectionMode() == S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD;
+ }
+
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInput.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java
index 4a920685a..fd3c4910c 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInput.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java
@@ -20,11 +20,18 @@ package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.CompressionAlgorithmTags;
import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.util.Passphrase;
+import java.nio.ByteBuffer;
import java.util.Date;
+import java.util.Map;
-public class PgpSignEncryptInput {
+import android.os.Parcel;
+import android.os.Parcelable;
+
+
+public class PgpSignEncryptInputParcel implements Parcelable {
protected String mVersionHeader = null;
protected boolean mEnableAsciiArmorOutput = false;
@@ -35,16 +42,68 @@ public class PgpSignEncryptInput {
protected long mSignatureMasterKeyId = Constants.key.none;
protected Long mSignatureSubKeyId = null;
protected int mSignatureHashAlgorithm = PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED;
- protected Passphrase mSignaturePassphrase = null;
protected long mAdditionalEncryptId = Constants.key.none;
- protected byte[] mNfcSignedHash = null;
- protected Date mNfcCreationTimestamp = null;
protected boolean mFailOnMissingEncryptionKeyIds = false;
protected String mCharset;
protected boolean mCleartextSignature;
protected boolean mDetachedSignature = false;
protected boolean mHiddenRecipients = false;
+ public PgpSignEncryptInputParcel() {
+
+ }
+
+ PgpSignEncryptInputParcel(Parcel source) {
+
+ ClassLoader loader = getClass().getClassLoader();
+
+ // we do all of those here, so the PgpSignEncryptInput class doesn't have to be parcelable
+ mVersionHeader = source.readString();
+ mEnableAsciiArmorOutput = source.readInt() == 1;
+ mCompressionId = source.readInt();
+ mEncryptionMasterKeyIds = source.createLongArray();
+ mSymmetricPassphrase = source.readParcelable(loader);
+ mSymmetricEncryptionAlgorithm = source.readInt();
+ mSignatureMasterKeyId = source.readLong();
+ mSignatureSubKeyId = source.readInt() == 1 ? source.readLong() : null;
+ mSignatureHashAlgorithm = source.readInt();
+ mAdditionalEncryptId = source.readLong();
+ mFailOnMissingEncryptionKeyIds = source.readInt() == 1;
+ mCharset = source.readString();
+ mCleartextSignature = source.readInt() == 1;
+ mDetachedSignature = source.readInt() == 1;
+ mHiddenRecipients = source.readInt() == 1;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mVersionHeader);
+ dest.writeInt(mEnableAsciiArmorOutput ? 1 : 0);
+ dest.writeInt(mCompressionId);
+ dest.writeLongArray(mEncryptionMasterKeyIds);
+ dest.writeParcelable(mSymmetricPassphrase, 0);
+ dest.writeInt(mSymmetricEncryptionAlgorithm);
+ dest.writeLong(mSignatureMasterKeyId);
+ if (mSignatureSubKeyId != null) {
+ dest.writeInt(1);
+ dest.writeLong(mSignatureSubKeyId);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeInt(mSignatureHashAlgorithm);
+ dest.writeLong(mAdditionalEncryptId);
+ dest.writeInt(mFailOnMissingEncryptionKeyIds ? 1 : 0);
+ dest.writeString(mCharset);
+ dest.writeInt(mCleartextSignature ? 1 : 0);
+ dest.writeInt(mDetachedSignature ? 1 : 0);
+ dest.writeInt(mHiddenRecipients ? 1 : 0);
+ }
+
public String getCharset() {
return mCharset;
}
@@ -57,37 +116,20 @@ public class PgpSignEncryptInput {
return mFailOnMissingEncryptionKeyIds;
}
- public Date getNfcCreationTimestamp() {
- return mNfcCreationTimestamp;
- }
-
- public byte[] getNfcSignedHash() {
- return mNfcSignedHash;
- }
-
public long getAdditionalEncryptId() {
return mAdditionalEncryptId;
}
- public PgpSignEncryptInput setAdditionalEncryptId(long additionalEncryptId) {
+ public PgpSignEncryptInputParcel setAdditionalEncryptId(long additionalEncryptId) {
mAdditionalEncryptId = additionalEncryptId;
return this;
}
- public Passphrase getSignaturePassphrase() {
- return mSignaturePassphrase;
- }
-
- public PgpSignEncryptInput setSignaturePassphrase(Passphrase signaturePassphrase) {
- mSignaturePassphrase = signaturePassphrase;
- return this;
- }
-
public int getSignatureHashAlgorithm() {
return mSignatureHashAlgorithm;
}
- public PgpSignEncryptInput setSignatureHashAlgorithm(int signatureHashAlgorithm) {
+ public PgpSignEncryptInputParcel setSignatureHashAlgorithm(int signatureHashAlgorithm) {
mSignatureHashAlgorithm = signatureHashAlgorithm;
return this;
}
@@ -96,7 +138,7 @@ public class PgpSignEncryptInput {
return mSignatureSubKeyId;
}
- public PgpSignEncryptInput setSignatureSubKeyId(long signatureSubKeyId) {
+ public PgpSignEncryptInputParcel setSignatureSubKeyId(long signatureSubKeyId) {
mSignatureSubKeyId = signatureSubKeyId;
return this;
}
@@ -105,7 +147,7 @@ public class PgpSignEncryptInput {
return mSignatureMasterKeyId;
}
- public PgpSignEncryptInput setSignatureMasterKeyId(long signatureMasterKeyId) {
+ public PgpSignEncryptInputParcel setSignatureMasterKeyId(long signatureMasterKeyId) {
mSignatureMasterKeyId = signatureMasterKeyId;
return this;
}
@@ -114,7 +156,7 @@ public class PgpSignEncryptInput {
return mSymmetricEncryptionAlgorithm;
}
- public PgpSignEncryptInput setSymmetricEncryptionAlgorithm(int symmetricEncryptionAlgorithm) {
+ public PgpSignEncryptInputParcel setSymmetricEncryptionAlgorithm(int symmetricEncryptionAlgorithm) {
mSymmetricEncryptionAlgorithm = symmetricEncryptionAlgorithm;
return this;
}
@@ -123,7 +165,7 @@ public class PgpSignEncryptInput {
return mSymmetricPassphrase;
}
- public PgpSignEncryptInput setSymmetricPassphrase(Passphrase symmetricPassphrase) {
+ public PgpSignEncryptInputParcel setSymmetricPassphrase(Passphrase symmetricPassphrase) {
mSymmetricPassphrase = symmetricPassphrase;
return this;
}
@@ -132,7 +174,7 @@ public class PgpSignEncryptInput {
return mEncryptionMasterKeyIds;
}
- public PgpSignEncryptInput setEncryptionMasterKeyIds(long[] encryptionMasterKeyIds) {
+ public PgpSignEncryptInputParcel setEncryptionMasterKeyIds(long[] encryptionMasterKeyIds) {
mEncryptionMasterKeyIds = encryptionMasterKeyIds;
return this;
}
@@ -141,7 +183,7 @@ public class PgpSignEncryptInput {
return mCompressionId;
}
- public PgpSignEncryptInput setCompressionId(int compressionId) {
+ public PgpSignEncryptInputParcel setCompressionId(int compressionId) {
mCompressionId = compressionId;
return this;
}
@@ -154,28 +196,22 @@ public class PgpSignEncryptInput {
return mVersionHeader;
}
- public PgpSignEncryptInput setVersionHeader(String versionHeader) {
+ public PgpSignEncryptInputParcel setVersionHeader(String versionHeader) {
mVersionHeader = versionHeader;
return this;
}
- public PgpSignEncryptInput setEnableAsciiArmorOutput(boolean enableAsciiArmorOutput) {
+ public PgpSignEncryptInputParcel setEnableAsciiArmorOutput(boolean enableAsciiArmorOutput) {
mEnableAsciiArmorOutput = enableAsciiArmorOutput;
return this;
}
- public PgpSignEncryptInput setFailOnMissingEncryptionKeyIds(boolean failOnMissingEncryptionKeyIds) {
+ public PgpSignEncryptInputParcel setFailOnMissingEncryptionKeyIds(boolean failOnMissingEncryptionKeyIds) {
mFailOnMissingEncryptionKeyIds = failOnMissingEncryptionKeyIds;
return this;
}
- public PgpSignEncryptInput setNfcState(byte[] signedHash, Date creationTimestamp) {
- mNfcSignedHash = signedHash;
- mNfcCreationTimestamp = creationTimestamp;
- return this;
- }
-
- public PgpSignEncryptInput setCleartextSignature(boolean cleartextSignature) {
+ public PgpSignEncryptInputParcel setCleartextSignature(boolean cleartextSignature) {
this.mCleartextSignature = cleartextSignature;
return this;
}
@@ -184,7 +220,7 @@ public class PgpSignEncryptInput {
return mCleartextSignature;
}
- public PgpSignEncryptInput setDetachedSignature(boolean detachedSignature) {
+ public PgpSignEncryptInputParcel setDetachedSignature(boolean detachedSignature) {
this.mDetachedSignature = detachedSignature;
return this;
}
@@ -193,7 +229,7 @@ public class PgpSignEncryptInput {
return mDetachedSignature;
}
- public PgpSignEncryptInput setHiddenRecipients(boolean hiddenRecipients) {
+ public PgpSignEncryptInputParcel setHiddenRecipients(boolean hiddenRecipients) {
this.mHiddenRecipients = hiddenRecipients;
return this;
}
@@ -201,5 +237,16 @@ public class PgpSignEncryptInput {
public boolean isHiddenRecipients() {
return mHiddenRecipients;
}
+
+ public static final Creator<PgpSignEncryptInputParcel> CREATOR = new Creator<PgpSignEncryptInputParcel>() {
+ public PgpSignEncryptInputParcel createFromParcel(final Parcel source) {
+ return new PgpSignEncryptInputParcel(source);
+ }
+
+ public PgpSignEncryptInputParcel[] newArray(final int size) {
+ return new PgpSignEncryptInputParcel[size];
+ }
+ };
+
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java
index 16a09e77b..8ecb30cdd 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java
@@ -33,7 +33,6 @@ import org.spongycastle.openpgp.PGPSignatureGenerator;
import org.spongycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator;
import org.spongycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder;
-import org.spongycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.BaseOperation;
@@ -44,11 +43,15 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.ProgressScaler;
+import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -72,7 +75,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
* <p/>
* For a high-level operation based on URIs, see SignEncryptOperation.
*
- * @see org.sufficientlysecure.keychain.pgp.PgpSignEncryptInput
+ * @see PgpSignEncryptInputParcel
* @see org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult
* @see org.sufficientlysecure.keychain.operations.SignEncryptOperation
*/
@@ -99,8 +102,8 @@ public class PgpSignEncryptOperation extends BaseOperation {
/**
* Signs and/or encrypts data based on parameters of class
*/
- public PgpSignEncryptResult execute(PgpSignEncryptInput input,
- InputData inputData, OutputStream outputStream) {
+ public PgpSignEncryptResult execute(PgpSignEncryptInputParcel input, CryptoInputParcel cryptoInput,
+ InputData inputData, OutputStream outputStream) {
int indent = 0;
OperationLog log = new OperationLog();
@@ -128,7 +131,7 @@ public class PgpSignEncryptOperation extends BaseOperation {
ArmoredOutputStream armorOut = null;
OutputStream out;
if (input.isEnableAsciiArmorOutput()) {
- armorOut = new ArmoredOutputStream(outputStream);
+ armorOut = new ArmoredOutputStream(new BufferedOutputStream(outputStream, 1 << 16));
if (input.getVersionHeader() != null) {
armorOut.setHeader("Version", input.getVersionHeader());
}
@@ -145,62 +148,62 @@ public class PgpSignEncryptOperation extends BaseOperation {
CanonicalizedSecretKey signingKey = null;
if (enableSignature) {
+ updateProgress(R.string.progress_extracting_signature_key, 0, 100);
+
try {
// fetch the indicated master key id (the one whose name we sign in)
CanonicalizedSecretKeyRing signingKeyRing =
mProviderHelper.getCanonicalizedSecretKeyRing(input.getSignatureMasterKeyId());
- long signKeyId;
- // use specified signing subkey, or find the one to use
- if (input.getSignatureSubKeyId() == null) {
- signKeyId = signingKeyRing.getSecretSignId();
- } else {
- signKeyId = input.getSignatureSubKeyId();
- }
-
// fetch the specific subkey to sign with, or just use the master key if none specified
- signingKey = signingKeyRing.getSecretKey(signKeyId);
+ signingKey = signingKeyRing.getSecretKey(input.getSignatureSubKeyId());
- } catch (ProviderHelper.NotFoundException | PgpGeneralException e) {
- log.add(LogType.MSG_PSE_ERROR_SIGN_KEY, indent);
- return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
- }
-
- // Make sure we are allowed to sign here!
- if (!signingKey.canSign()) {
- log.add(LogType.MSG_PSE_ERROR_KEY_SIGN, indent);
- return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
- }
-
- // if no passphrase was explicitly set try to get it from the cache service
- if (input.getSignaturePassphrase() == null) {
- try {
- // returns "" if key has no passphrase
- input.setSignaturePassphrase(getCachedPassphrase(signingKey.getKeyId()));
- // TODO
-// log.add(LogType.MSG_DC_PASS_CACHED, indent + 1);
- } catch (PassphraseCacheInterface.NoSecretKeyException e) {
- // TODO
-// log.add(LogType.MSG_DC_ERROR_NO_KEY, indent + 1);
+ // Make sure we are allowed to sign here!
+ if (!signingKey.canSign()) {
+ log.add(LogType.MSG_PSE_ERROR_KEY_SIGN, indent);
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
}
- // if passphrase was not cached, return here indicating that a passphrase is missing!
- if (input.getSignaturePassphrase() == null) {
- log.add(LogType.MSG_PSE_PENDING_PASSPHRASE, indent + 1);
- PgpSignEncryptResult result = new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE, log);
- result.setKeyIdPassphraseNeeded(signingKey.getKeyId());
- return result;
- }
- }
+ switch (signingKey.getSecretKeyType()) {
+ case DIVERT_TO_CARD:
+ case PASSPHRASE_EMPTY: {
+ if (!signingKey.unlock(new Passphrase())) {
+ throw new AssertionError(
+ "PASSPHRASE_EMPTY/DIVERT_TO_CARD keyphrase not unlocked with empty passphrase."
+ + " This is a programming error!");
+ }
+ break;
+ }
- updateProgress(R.string.progress_extracting_signature_key, 0, 100);
+ case PIN:
+ case PATTERN:
+ case PASSPHRASE: {
+ if (cryptoInput.getPassphrase() == null) {
+ log.add(LogType.MSG_PSE_PENDING_PASSPHRASE, indent + 1);
+ return new PgpSignEncryptResult(log, RequiredInputParcel.createRequiredSignPassphrase(
+ signingKeyRing.getMasterKeyId(), signingKey.getKeyId(),
+ cryptoInput.getSignatureTime()));
+ }
+ if (!signingKey.unlock(cryptoInput.getPassphrase())) {
+ log.add(LogType.MSG_PSE_ERROR_BAD_PASSPHRASE, indent);
+ return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
+ }
+ break;
+ }
+
+ case GNU_DUMMY: {
+ log.add(LogType.MSG_PSE_ERROR_UNLOCK, indent);
+ return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
+ }
+ default: {
+ throw new AssertionError("Unhandled SecretKeyType! (should not happen)");
+ }
- try {
- if (!signingKey.unlock(input.getSignaturePassphrase())) {
- log.add(LogType.MSG_PSE_ERROR_BAD_PASSPHRASE, indent);
- return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
}
+
+ } catch (ProviderHelper.NotFoundException e) {
+ log.add(LogType.MSG_PSE_ERROR_SIGN_KEY, indent);
+ return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
} catch (PgpGeneralException e) {
log.add(LogType.MSG_PSE_ERROR_UNLOCK, indent);
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
@@ -281,8 +284,9 @@ public class PgpSignEncryptOperation extends BaseOperation {
try {
boolean cleartext = input.isCleartextSignature() && input.isEnableAsciiArmorOutput() && !enableEncryption;
- signatureGenerator = signingKey.getSignatureGenerator(
- input.getSignatureHashAlgorithm(), cleartext, input.getNfcSignedHash(), input.getNfcCreationTimestamp());
+ signatureGenerator = signingKey.getDataSignatureGenerator(
+ input.getSignatureHashAlgorithm(), cleartext,
+ cryptoInput.getCryptoData(), cryptoInput.getSignatureTime());
} catch (PgpGeneralException e) {
log.add(LogType.MSG_PSE_ERROR_NFC, indent);
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
@@ -405,7 +409,7 @@ public class PgpSignEncryptOperation extends BaseOperation {
detachedByteOut = new ByteArrayOutputStream();
OutputStream detachedOut = detachedByteOut;
if (input.isEnableAsciiArmorOutput()) {
- detachedArmorOut = new ArmoredOutputStream(detachedOut);
+ detachedArmorOut = new ArmoredOutputStream(new BufferedOutputStream(detachedOut, 1 << 16));
if (input.getVersionHeader() != null) {
detachedArmorOut.setHeader("Version", input.getVersionHeader());
}
@@ -485,19 +489,8 @@ public class PgpSignEncryptOperation extends BaseOperation {
} catch (NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded e) {
// this secret key diverts to a OpenPGP card, throw exception with hash that will be signed
log.add(LogType.MSG_PSE_PENDING_NFC, indent);
- PgpSignEncryptResult result =
- new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_PENDING_NFC, log);
-
- // SignatureSubKeyId can be null.
- if (input.getSignatureSubKeyId() == null) {
- return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
- }
-
- // Note that the checked key here is the master key, not the signing key
- // (although these are always the same on Yubikeys)
- result.setNfcData(input.getSignatureSubKeyId(), e.hashToSign, e.hashAlgo, e.creationTimestamp, input.getSignaturePassphrase());
- Log.d(Constants.TAG, "e.hashToSign" + Hex.toHexString(e.hashToSign));
- return result;
+ return new PgpSignEncryptResult(log, RequiredInputParcel.createNfcSignOperation(
+ e.hashToSign, e.hashAlgo, cryptoInput.getSignatureTime()));
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/SignEncryptParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/SignEncryptParcel.java
index 975548c95..464de37f5 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/SignEncryptParcel.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/SignEncryptParcel.java
@@ -20,14 +20,10 @@ package org.sufficientlysecure.keychain.pgp;
import android.net.Uri;
import android.os.Parcel;
-import android.os.Parcelable;
-
-import org.sufficientlysecure.keychain.util.Passphrase;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.Date;
import java.util.List;
/** This parcel stores the input of one or more PgpSignEncrypt operations.
@@ -42,7 +38,7 @@ import java.util.List;
* left, which will be returned in a byte array as part of the result parcel.
*
*/
-public class SignEncryptParcel extends PgpSignEncryptInput implements Parcelable {
+public class SignEncryptParcel extends PgpSignEncryptInputParcel {
public ArrayList<Uri> mInputUris = new ArrayList<>();
public ArrayList<Uri> mOutputUris = new ArrayList<>();
@@ -53,26 +49,7 @@ public class SignEncryptParcel extends PgpSignEncryptInput implements Parcelable
}
public SignEncryptParcel(Parcel src) {
-
- // we do all of those here, so the PgpSignEncryptInput class doesn't have to be parcelable
- mVersionHeader = src.readString();
- mEnableAsciiArmorOutput = src.readInt() == 1;
- mCompressionId = src.readInt();
- mEncryptionMasterKeyIds = src.createLongArray();
- mSymmetricPassphrase = src.readParcelable(Passphrase.class.getClassLoader());
- mSymmetricEncryptionAlgorithm = src.readInt();
- mSignatureMasterKeyId = src.readLong();
- mSignatureSubKeyId = src.readInt() == 1 ? src.readLong() : null;
- mSignatureHashAlgorithm = src.readInt();
- mSignaturePassphrase = src.readParcelable(Passphrase.class.getClassLoader());
- mAdditionalEncryptId = src.readLong();
- mNfcSignedHash = src.createByteArray();
- mNfcCreationTimestamp = src.readInt() == 1 ? new Date(src.readLong()) : null;
- mFailOnMissingEncryptionKeyIds = src.readInt() == 1;
- mCharset = src.readString();
- mCleartextSignature = src.readInt() == 1;
- mDetachedSignature = src.readInt() == 1;
- mHiddenRecipients = src.readInt() == 1;
+ super(src);
mInputUris = src.createTypedArrayList(Uri.CREATOR);
mOutputUris = src.createTypedArrayList(Uri.CREATOR);
@@ -110,34 +87,7 @@ public class SignEncryptParcel extends PgpSignEncryptInput implements Parcelable
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(mVersionHeader);
- dest.writeInt(mEnableAsciiArmorOutput ? 1 : 0);
- dest.writeInt(mCompressionId);
- dest.writeLongArray(mEncryptionMasterKeyIds);
- dest.writeParcelable(mSymmetricPassphrase, flags);
- dest.writeInt(mSymmetricEncryptionAlgorithm);
- dest.writeLong(mSignatureMasterKeyId);
- if (mSignatureSubKeyId != null) {
- dest.writeInt(1);
- dest.writeLong(mSignatureSubKeyId);
- } else {
- dest.writeInt(0);
- }
- dest.writeInt(mSignatureHashAlgorithm);
- dest.writeParcelable(mSignaturePassphrase, flags);
- dest.writeLong(mAdditionalEncryptId);
- dest.writeByteArray(mNfcSignedHash);
- if (mNfcCreationTimestamp != null) {
- dest.writeInt(1);
- dest.writeLong(mNfcCreationTimestamp.getTime());
- } else {
- dest.writeInt(0);
- }
- dest.writeInt(mFailOnMissingEncryptionKeyIds ? 1 : 0);
- dest.writeString(mCharset);
- dest.writeInt(mCleartextSignature ? 1 : 0);
- dest.writeInt(mDetachedSignature ? 1 : 0);
- dest.writeInt(mHiddenRecipients ? 1 : 0);
+ super.writeToParcel(dest, flags);
dest.writeTypedList(mInputUris);
dest.writeTypedList(mOutputUris);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java
index 681aff56d..b86618a9a 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java
@@ -1165,7 +1165,7 @@ public class UncachedKeyRing {
}
}
- // If anything changed, save the updated (sub)key
+ // If anything change, save the updated (sub)key
if (modified != resultKey) {
result = replacePublicKey(result, modified);
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/CryptoInputParcelCacheService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/CryptoInputParcelCacheService.java
new file mode 100644
index 000000000..e3e39417a
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/CryptoInputParcelCacheService.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2015 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.remote;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+
+import org.openintents.openpgp.util.OpenPgpApi;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+
+public class CryptoInputParcelCacheService extends Service {
+
+ public static final String ACTION_ADD = Constants.INTENT_PREFIX + "ADD";
+ public static final String ACTION_GET = Constants.INTENT_PREFIX + "GET";
+
+ public static final String EXTRA_CRYPTO_INPUT_PARCEL = "crypto_input_parcel";
+ public static final String EXTRA_UUID1 = "uuid1";
+ public static final String EXTRA_UUID2 = "uuid2";
+ public static final String EXTRA_MESSENGER = "messenger";
+
+ private static final int MSG_GET_OKAY = 1;
+ private static final int MSG_GET_NOT_FOUND = 2;
+
+ Context mContext;
+
+ private static final UUID NULL_UUID = new UUID(0, 0);
+
+ private ConcurrentHashMap<UUID, CryptoInputParcel> mCache = new ConcurrentHashMap<>();
+
+ public static class InputParcelNotFound extends Exception {
+ public InputParcelNotFound() {
+ }
+
+ public InputParcelNotFound(String name) {
+ super(name);
+ }
+ }
+
+ public static void addCryptoInputParcel(Context context, Intent data, CryptoInputParcel inputParcel) {
+ UUID mTicket = addCryptoInputParcel(context, inputParcel);
+ // And write out the UUID most and least significant bits.
+ data.putExtra(OpenPgpApi.EXTRA_CALL_UUID1, mTicket.getMostSignificantBits());
+ data.putExtra(OpenPgpApi.EXTRA_CALL_UUID2, mTicket.getLeastSignificantBits());
+ }
+
+ public static CryptoInputParcel getCryptoInputParcel(Context context, Intent data) {
+ if (!data.getExtras().containsKey(OpenPgpApi.EXTRA_CALL_UUID1)
+ || !data.getExtras().containsKey(OpenPgpApi.EXTRA_CALL_UUID2)) {
+ return null;
+ }
+ long mostSig = data.getLongExtra(OpenPgpApi.EXTRA_CALL_UUID1, 0);
+ long leastSig = data.getLongExtra(OpenPgpApi.EXTRA_CALL_UUID2, 0);
+ UUID uuid = new UUID(mostSig, leastSig);
+ try {
+ return getCryptoInputParcel(context, uuid);
+ } catch (InputParcelNotFound inputParcelNotFound) {
+ return null;
+ }
+ }
+
+ private static UUID addCryptoInputParcel(Context context, CryptoInputParcel inputParcel) {
+ UUID uuid = UUID.randomUUID();
+
+ Intent intent = new Intent(context, CryptoInputParcelCacheService.class);
+ intent.setAction(ACTION_ADD);
+ intent.putExtra(EXTRA_CRYPTO_INPUT_PARCEL, inputParcel);
+ intent.putExtra(EXTRA_UUID1, uuid.getMostSignificantBits());
+ intent.putExtra(EXTRA_UUID2, uuid.getLeastSignificantBits());
+ context.startService(intent);
+ return uuid;
+ }
+
+ private static CryptoInputParcel getCryptoInputParcel(Context context, UUID uuid) throws InputParcelNotFound {
+ Intent intent = new Intent(context, CryptoInputParcelCacheService.class);
+ intent.setAction(ACTION_GET);
+
+ final Object mutex = new Object();
+ final Message returnMessage = Message.obtain();
+
+ HandlerThread handlerThread = new HandlerThread("getParcelableThread");
+ handlerThread.start();
+ Handler returnHandler = new Handler(handlerThread.getLooper()) {
+ @Override
+ public void handleMessage(Message message) {
+ // copy over result to handle after mutex.wait
+ returnMessage.what = message.what;
+ returnMessage.copyFrom(message);
+ synchronized (mutex) {
+ mutex.notify();
+ }
+ // quit handlerThread
+ getLooper().quit();
+ }
+ };
+
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(returnHandler);
+ intent.putExtra(EXTRA_UUID1, uuid.getMostSignificantBits());
+ intent.putExtra(EXTRA_UUID2, uuid.getLeastSignificantBits());
+ intent.putExtra(EXTRA_MESSENGER, messenger);
+ // send intent to this service
+ context.startService(intent);
+
+ // Wait on mutex until parcelable is returned to handlerThread. Note that this local
+ // variable is used in the handler closure above, so it does make sense here!
+ // noinspection SynchronizationOnLocalVariableOrMethodParameter
+ synchronized (mutex) {
+ try {
+ mutex.wait(3000);
+ } catch (InterruptedException e) {
+ // don't care
+ }
+ }
+
+ switch (returnMessage.what) {
+ case MSG_GET_OKAY:
+ Bundle returnData = returnMessage.getData();
+ returnData.setClassLoader(context.getClassLoader());
+ return returnData.getParcelable(EXTRA_CRYPTO_INPUT_PARCEL);
+ case MSG_GET_NOT_FOUND:
+ throw new InputParcelNotFound();
+ default:
+ Log.e(Constants.TAG, "timeout!");
+ throw new InputParcelNotFound("should not happen!");
+ }
+ }
+
+ /**
+ * Executed when service is started by intent
+ */
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+
+ if (intent == null || intent.getAction() == null) {
+ return START_NOT_STICKY;
+ }
+
+ String action = intent.getAction();
+ switch (action) {
+ case ACTION_ADD: {
+ long uuid1 = intent.getLongExtra(EXTRA_UUID1, 0);
+ long uuid2 = intent.getLongExtra(EXTRA_UUID2, 0);
+ UUID uuid = new UUID(uuid1, uuid2);
+ CryptoInputParcel inputParcel = intent.getParcelableExtra(EXTRA_CRYPTO_INPUT_PARCEL);
+ mCache.put(uuid, inputParcel);
+
+ break;
+ }
+ case ACTION_GET: {
+ long uuid1 = intent.getLongExtra(EXTRA_UUID1, 0);
+ long uuid2 = intent.getLongExtra(EXTRA_UUID2, 0);
+ UUID uuid = new UUID(uuid1, uuid2);
+ Messenger messenger = intent.getParcelableExtra(EXTRA_MESSENGER);
+
+ Message msg = Message.obtain();
+ // UUID.equals isn't well documented; we use compareTo instead.
+ if (NULL_UUID.compareTo(uuid) == 0) {
+ msg.what = MSG_GET_NOT_FOUND;
+ } else {
+ CryptoInputParcel inputParcel = mCache.get(uuid);
+ mCache.remove(uuid);
+ msg.what = MSG_GET_OKAY;
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(EXTRA_CRYPTO_INPUT_PARCEL, inputParcel);
+ msg.setData(bundle);
+ }
+
+ try {
+ messenger.send(msg);
+ } catch (RemoteException e) {
+ Log.e(Constants.TAG, "CryptoInputParcelCacheService: Sending message failed", e);
+ }
+ break;
+ }
+ default: {
+ Log.e(Constants.TAG, "CryptoInputParcelCacheService: Intent or Intent Action not supported!");
+ break;
+ }
+ }
+
+ if (mCache.size() <= 0) {
+ // stop whole service if cache is empty
+ Log.d(Constants.TAG, "CryptoInputParcelCacheService: No passphrases remaining in memory, stopping service!");
+ stopSelf();
+ }
+
+ return START_NOT_STICKY;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mContext = this;
+ Log.d(Constants.TAG, "CryptoInputParcelCacheService, onCreate()");
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ Log.d(Constants.TAG, "CryptoInputParcelCacheService, onDestroy()");
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+ public class CryptoInputParcelCacheServiceBinder extends Binder {
+ public CryptoInputParcelCacheService getService() {
+ return CryptoInputParcelCacheService.this;
+ }
+ }
+
+ private final IBinder mBinder = new CryptoInputParcelCacheServiceBinder();
+
+} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java
index bd2866985..c51edf59c 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java
@@ -18,6 +18,7 @@
package org.sufficientlysecure.keychain.remote;
import android.app.PendingIntent;
+import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
@@ -31,16 +32,15 @@ import org.openintents.openpgp.OpenPgpMetadata;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.openintents.openpgp.util.OpenPgpApi;
import org.spongycastle.bcpg.CompressionAlgorithmTags;
-import org.spongycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEntryParcel;
import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;
import org.sufficientlysecure.keychain.pgp.PgpConstants;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
-import org.sufficientlysecure.keychain.pgp.PgpSignEncryptInput;
+import org.sufficientlysecure.keychain.pgp.PgpSignEncryptInputParcel;
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation;
-import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
+import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAccounts;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
@@ -48,8 +48,10 @@ import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.remote.ui.RemoteServiceActivity;
import org.sufficientlysecure.keychain.remote.ui.SelectSignKeyIdActivity;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.ImportKeysActivity;
-import org.sufficientlysecure.keychain.ui.NfcActivity;
+import org.sufficientlysecure.keychain.ui.NfcOperationActivity;
import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity;
import org.sufficientlysecure.keychain.ui.ViewKeyActivity;
import org.sufficientlysecure.keychain.util.InputData;
@@ -60,7 +62,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
-import java.util.Date;
import java.util.Set;
public class OpenPgpService extends RemoteService {
@@ -78,9 +79,6 @@ public class OpenPgpService extends RemoteService {
/**
* Search database for key ids based on emails.
- *
- * @param encryptionUserIds
- * @return
*/
private Intent returnKeyIdsFromEmails(Intent data, String[] encryptionUserIds) {
boolean noUserIdsCheck = (encryptionUserIds == null || encryptionUserIds.length == 0);
@@ -163,52 +161,35 @@ public class OpenPgpService extends RemoteService {
}
}
- private Intent returnPassphraseIntent(Intent data, long keyId) {
- // build PendingIntent for passphrase input
- Intent intent = new Intent(getBaseContext(), PassphraseDialogActivity.class);
- intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, keyId);
- // pass params through to activity that it can be returned again later to repeat pgp operation
- intent.putExtra(PassphraseDialogActivity.EXTRA_DATA, data);
- PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
- intent,
- PendingIntent.FLAG_CANCEL_CURRENT);
-
- // return PendingIntent to be executed by client
- Intent result = new Intent();
- result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
- result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
- return result;
- }
+ private static PendingIntent getRequiredInputPendingIntent(Context context,
+ Intent data, RequiredInputParcel requiredInput) {
+
+ switch (requiredInput.mType) {
+ case NFC_DECRYPT:
+ case NFC_SIGN: {
+ // build PendingIntent for YubiKey NFC operations
+ Intent intent = new Intent(context, NfcOperationActivity.class);
+ // pass params through to activity that it can be returned again later to repeat pgp operation
+ intent.putExtra(NfcOperationActivity.EXTRA_SERVICE_INTENT, data);
+ intent.putExtra(NfcOperationActivity.EXTRA_REQUIRED_INPUT, requiredInput);
+ return PendingIntent.getActivity(context, 0, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT);
+ }
- private PendingIntent getNfcSignPendingIntent(Intent data, long keyId, Passphrase pin, byte[] hashToSign, int hashAlgo) {
- // build PendingIntent for Yubikey NFC operations
- Intent intent = new Intent(getBaseContext(), NfcActivity.class);
- intent.setAction(NfcActivity.ACTION_SIGN_HASH);
- // pass params through to activity that it can be returned again later to repeat pgp operation
- intent.putExtra(NfcActivity.EXTRA_DATA, data);
- intent.putExtra(NfcActivity.EXTRA_PIN, pin);
- intent.putExtra(NfcActivity.EXTRA_KEY_ID, keyId);
-
- intent.putExtra(NfcActivity.EXTRA_NFC_HASH_TO_SIGN, hashToSign);
- intent.putExtra(NfcActivity.EXTRA_NFC_HASH_ALGO, hashAlgo);
- return PendingIntent.getActivity(getBaseContext(), 0,
- intent,
- PendingIntent.FLAG_CANCEL_CURRENT);
- }
+ case PASSPHRASE: {
+ // build PendingIntent for Passphrase request
+ Intent intent = new Intent(context, PassphraseDialogActivity.class);
+ // pass params through to activity that it can be returned again later to repeat pgp operation
+ intent.putExtra(PassphraseDialogActivity.EXTRA_SERVICE_INTENT, data);
+ intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT, requiredInput);
+ return PendingIntent.getActivity(context, 0, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT);
+ }
- private PendingIntent getNfcDecryptPendingIntent(Intent data, long subKeyId, Passphrase pin, byte[] encryptedSessionKey) {
- // build PendingIntent for Yubikey NFC operations
- Intent intent = new Intent(getBaseContext(), NfcActivity.class);
- intent.setAction(NfcActivity.ACTION_DECRYPT_SESSION_KEY);
- // pass params through to activity that it can be returned again later to repeat pgp operation
- intent.putExtra(NfcActivity.EXTRA_DATA, data);
- intent.putExtra(NfcActivity.EXTRA_PIN, pin);
- intent.putExtra(NfcActivity.EXTRA_KEY_ID, subKeyId);
+ default:
+ throw new AssertionError("Unhandled required input type!");
+ }
- intent.putExtra(NfcActivity.EXTRA_NFC_ENC_SESSION_KEY, encryptedSessionKey);
- return PendingIntent.getActivity(getBaseContext(), 0,
- intent,
- PendingIntent.FLAG_CANCEL_CURRENT);
}
private PendingIntent getKeyserverPendingIntent(Intent data, long masterKeyId) {
@@ -240,17 +221,13 @@ public class OpenPgpService extends RemoteService {
try {
boolean asciiArmor = cleartextSign || data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
- Passphrase passphrase = null;
- if (data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE) != null) {
- passphrase = new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE));
- }
-
- byte[] nfcSignedHash = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_SIGNED_HASH);
- if (nfcSignedHash != null) {
- Log.d(Constants.TAG, "nfcSignedHash:" + Hex.toHexString(nfcSignedHash));
- } else {
- Log.d(Constants.TAG, "nfcSignedHash: null");
- }
+ // sign-only
+ PgpSignEncryptInputParcel pseInput = new PgpSignEncryptInputParcel()
+ .setEnableAsciiArmorOutput(asciiArmor)
+ .setCleartextSignature(cleartextSign)
+ .setDetachedSignature(!cleartextSign)
+ .setVersionHeader(null)
+ .setSignatureHashAlgorithm(PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED);
Intent signKeyIdIntent = getSignKeyMasterId(data);
// NOTE: Fallback to return account settings (Old API)
@@ -258,17 +235,21 @@ public class OpenPgpService extends RemoteService {
== OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED) {
return signKeyIdIntent;
}
+
long signKeyId = signKeyIdIntent.getLongExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, Constants.key.none);
if (signKeyId == Constants.key.none) {
- Log.e(Constants.TAG, "No signing key given!");
- }
+ throw new Exception("No signing key given");
+ } else {
+ pseInput.setSignatureMasterKeyId(signKeyId);
- // carefully: only set if timestamp exists
- Date nfcCreationDate = null;
- long nfcCreationTimestamp = data.getLongExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, -1);
- Log.d(Constants.TAG, "nfcCreationTimestamp: " + nfcCreationTimestamp);
- if (nfcCreationTimestamp != -1) {
- nfcCreationDate = new Date(nfcCreationTimestamp);
+ // get first usable subkey capable of signing
+ try {
+ long signSubKeyId = mProviderHelper.getCachedPublicKeyRing(
+ pseInput.getSignatureMasterKeyId()).getSecretSignId();
+ pseInput.setSignatureSubKeyId(signSubKeyId);
+ } catch (PgpKeyNotFoundException e) {
+ throw new Exception("signing subkey not found!", e);
+ }
}
// Get Input- and OutputStream from ParcelFileDescriptor
@@ -281,42 +262,31 @@ public class OpenPgpService extends RemoteService {
long inputLength = is.available();
InputData inputData = new InputData(is, inputLength);
- // sign-only
- PgpSignEncryptInput pseInput = new PgpSignEncryptInput()
- .setSignaturePassphrase(passphrase)
- .setEnableAsciiArmorOutput(asciiArmor)
- .setCleartextSignature(cleartextSign)
- .setDetachedSignature(!cleartextSign)
- .setVersionHeader(null)
- .setSignatureHashAlgorithm(PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED)
- .setSignatureMasterKeyId(signKeyId)
- .setNfcState(nfcSignedHash, nfcCreationDate);
+ CryptoInputParcel inputParcel = CryptoInputParcelCacheService.getCryptoInputParcel(this, data);
+ if (inputParcel == null) {
+ inputParcel = new CryptoInputParcel();
+ }
+ // override passphrase in input parcel if given by API call
+ if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
+ inputParcel = new CryptoInputParcel(inputParcel.getSignatureTime(),
+ new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE)));
+ }
// execute PGP operation!
PgpSignEncryptOperation pse = new PgpSignEncryptOperation(this, new ProviderHelper(getContext()), null);
- PgpSignEncryptResult pgpResult = pse.execute(pseInput, inputData, os);
+ PgpSignEncryptResult pgpResult = pse.execute(pseInput, inputParcel, inputData, os);
if (pgpResult.isPending()) {
- if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE) ==
- PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE) {
- return returnPassphraseIntent(data, pgpResult.getKeyIdPassphraseNeeded());
- } else if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_NFC) ==
- PgpSignEncryptResult.RESULT_PENDING_NFC) {
- // return PendingIntent to execute NFC activity
- // pass through the signature creation timestamp to be used again on second execution
- // of PgpSignEncrypt when we have the signed hash!
- data.putExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, pgpResult.getNfcTimestamp().getTime());
-
- // return PendingIntent to be executed by client
- Intent result = new Intent();
- result.putExtra(OpenPgpApi.RESULT_INTENT,
- getNfcSignPendingIntent(data, pgpResult.getNfcKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcHash(), pgpResult.getNfcAlgo()));
- result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
- return result;
- } else {
- throw new PgpGeneralException(
- "Encountered unhandled type of pending action not supported by API!");
- }
+
+ RequiredInputParcel requiredInput = pgpResult.getRequiredInputParcel();
+ PendingIntent pIntent = getRequiredInputPendingIntent(getBaseContext(), data, requiredInput);
+
+ // return PendingIntent to be executed by client
+ Intent result = new Intent();
+ result.putExtra(OpenPgpApi.RESULT_INTENT, pIntent);
+ result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
+ return result;
+
} else if (pgpResult.success()) {
Intent result = new Intent();
if (pgpResult.getDetachedSignature() != null && !cleartextSign) {
@@ -372,11 +342,6 @@ public class OpenPgpService extends RemoteService {
compressionId = CompressionAlgorithmTags.UNCOMPRESSED;
}
- Passphrase passphrase = null;
- if (data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE) != null) {
- passphrase = new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE));
- }
-
// first try to get key ids from non-ambiguous key id extra
long[] keyIds = data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS);
if (keyIds == null) {
@@ -401,9 +366,8 @@ public class OpenPgpService extends RemoteService {
long inputLength = is.available();
InputData inputData = new InputData(is, inputLength, originalFilename);
- PgpSignEncryptInput pseInput = new PgpSignEncryptInput();
- pseInput.setSignaturePassphrase(passphrase)
- .setEnableAsciiArmorOutput(asciiArmor)
+ PgpSignEncryptInputParcel pseInput = new PgpSignEncryptInputParcel();
+ pseInput.setEnableAsciiArmorOutput(asciiArmor)
.setVersionHeader(null)
.setCompressionId(compressionId)
.setSymmetricEncryptionAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED)
@@ -420,49 +384,49 @@ public class OpenPgpService extends RemoteService {
}
long signKeyId = signKeyIdIntent.getLongExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, Constants.key.none);
if (signKeyId == Constants.key.none) {
- Log.e(Constants.TAG, "No signing key given!");
- }
+ throw new Exception("No signing key given");
+ } else {
+ pseInput.setSignatureMasterKeyId(signKeyId);
- byte[] nfcSignedHash = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_SIGNED_HASH);
- // carefully: only set if timestamp exists
- Date nfcCreationDate = null;
- long nfcCreationTimestamp = data.getLongExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, -1);
- if (nfcCreationTimestamp != -1) {
- nfcCreationDate = new Date(nfcCreationTimestamp);
+ // get first usable subkey capable of signing
+ try {
+ long signSubKeyId = mProviderHelper.getCachedPublicKeyRing(
+ pseInput.getSignatureMasterKeyId()).getSecretSignId();
+ pseInput.setSignatureSubKeyId(signSubKeyId);
+ } catch (PgpKeyNotFoundException e) {
+ throw new Exception("signing subkey not found!", e);
+ }
}
// sign and encrypt
pseInput.setSignatureHashAlgorithm(PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED)
- .setSignatureMasterKeyId(signKeyId)
- .setNfcState(nfcSignedHash, nfcCreationDate)
.setAdditionalEncryptId(signKeyId); // add sign key for encryption
}
+ CryptoInputParcel inputParcel = CryptoInputParcelCacheService.getCryptoInputParcel(this, data);
+ if (inputParcel == null) {
+ inputParcel = new CryptoInputParcel();
+ }
+ // override passphrase in input parcel if given by API call
+ if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
+ inputParcel = new CryptoInputParcel(inputParcel.getSignatureTime(),
+ new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE)));
+ }
+
PgpSignEncryptOperation op = new PgpSignEncryptOperation(this, new ProviderHelper(getContext()), null);
// execute PGP operation!
- PgpSignEncryptResult pgpResult = op.execute(pseInput, inputData, os);
+ PgpSignEncryptResult pgpResult = op.execute(pseInput, inputParcel, inputData, os);
if (pgpResult.isPending()) {
- if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE) ==
- PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE) {
- return returnPassphraseIntent(data, pgpResult.getKeyIdPassphraseNeeded());
- } else if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_NFC) ==
- PgpSignEncryptResult.RESULT_PENDING_NFC) {
- // return PendingIntent to execute NFC activity
- // pass through the signature creation timestamp to be used again on second execution
- // of PgpSignEncrypt when we have the signed hash!
- data.putExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, pgpResult.getNfcTimestamp().getTime());
- // return PendingIntent to be executed by client
- Intent result = new Intent();
- result.putExtra(OpenPgpApi.RESULT_INTENT,
- getNfcSignPendingIntent(data, pgpResult.getNfcKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcHash(), pgpResult.getNfcAlgo()));
- result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
- return result;
- } else {
- throw new PgpGeneralException(
- "Encountered unhandled type of pending action not supported by API!");
- }
+ RequiredInputParcel requiredInput = pgpResult.getRequiredInputParcel();
+ PendingIntent pIntent = getRequiredInputPendingIntent(getBaseContext(), data, requiredInput);
+
+ // return PendingIntent to be executed by client
+ Intent result = new Intent();
+ result.putExtra(OpenPgpApi.RESULT_INTENT, pIntent);
+ result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
+ return result;
} else if (pgpResult.success()) {
Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
@@ -511,11 +475,6 @@ public class OpenPgpService extends RemoteService {
os = new ParcelFileDescriptor.AutoCloseOutputStream(output);
}
- Passphrase passphrase = null;
- if (data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE) != null) {
- passphrase = new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE));
- }
-
String currentPkg = getCurrentCallingPackage();
Set<Long> allowedKeyIds;
if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) < 7) {
@@ -533,42 +492,37 @@ public class OpenPgpService extends RemoteService {
this, new ProviderHelper(getContext()), null, inputData, os
);
- byte[] nfcDecryptedSessionKey = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_DECRYPTED_SESSION_KEY);
+ CryptoInputParcel inputParcel = CryptoInputParcelCacheService.getCryptoInputParcel(this, data);
+ if (inputParcel == null) {
+ inputParcel = new CryptoInputParcel();
+ }
+ // override passphrase in input parcel if given by API call
+ if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
+ inputParcel = new CryptoInputParcel(inputParcel.getSignatureTime(),
+ new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE)));
+ }
byte[] detachedSignature = data.getByteArrayExtra(OpenPgpApi.EXTRA_DETACHED_SIGNATURE);
// allow only private keys associated with accounts of this app
// no support for symmetric encryption
- builder.setPassphrase(passphrase)
- .setAllowSymmetricDecryption(false)
+ builder.setAllowSymmetricDecryption(false)
.setAllowedKeyIds(allowedKeyIds)
.setDecryptMetadataOnly(decryptMetadataOnly)
- .setNfcState(nfcDecryptedSessionKey)
.setDetachedSignature(detachedSignature);
- DecryptVerifyResult pgpResult = builder.build().execute();
+ DecryptVerifyResult pgpResult = builder.build().execute(inputParcel);
if (pgpResult.isPending()) {
- if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) ==
- DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) {
- return returnPassphraseIntent(data, pgpResult.getKeyIdPassphraseNeeded());
- } else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) ==
- DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) {
- throw new PgpGeneralException(
- "Decryption of symmetric content not supported by API!");
- } else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_NFC) ==
- DecryptVerifyResult.RESULT_PENDING_NFC) {
-
- // return PendingIntent to be executed by client
- Intent result = new Intent();
- result.putExtra(OpenPgpApi.RESULT_INTENT,
- getNfcDecryptPendingIntent(data, pgpResult.getNfcSubKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcEncryptedSessionKey()));
- result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
- return result;
- } else {
- throw new PgpGeneralException(
- "Encountered unhandled type of pending action not supported by API!");
- }
+ // prepare and return PendingIntent to be executed by client
+ RequiredInputParcel requiredInput = pgpResult.getRequiredInputParcel();
+ PendingIntent pIntent = getRequiredInputPendingIntent(getBaseContext(), data, requiredInput);
+
+ Intent result = new Intent();
+ result.putExtra(OpenPgpApi.RESULT_INTENT, pIntent);
+ result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
+ return result;
+
} else if (pgpResult.success()) {
Intent result = new Intent();
@@ -754,7 +708,6 @@ public class OpenPgpService extends RemoteService {
* - has supported API version
* - is allowed to call the service (access has been granted)
*
- * @param data
* @return null if everything is okay, or a Bundle with an error/PendingIntent
*/
private Intent checkRequirements(Intent data) {
@@ -794,9 +747,7 @@ public class OpenPgpService extends RemoteService {
return null;
}
- // TODO: multi-threading
private final IOpenPgpService.Stub mBinder = new IOpenPgpService.Stub() {
-
@Override
public Intent execute(Intent data, ParcelFileDescriptor input, ParcelFileDescriptor output) {
try {
@@ -806,30 +757,42 @@ public class OpenPgpService extends RemoteService {
}
String action = data.getAction();
- if (OpenPgpApi.ACTION_CLEARTEXT_SIGN.equals(action)) {
- return signImpl(data, input, output, true);
- } else if (OpenPgpApi.ACTION_SIGN.equals(action)) {
- // DEPRECATED: same as ACTION_CLEARTEXT_SIGN
- Log.w(Constants.TAG, "You are using a deprecated API call, please use ACTION_CLEARTEXT_SIGN instead of ACTION_SIGN!");
- return signImpl(data, input, output, true);
- } else if (OpenPgpApi.ACTION_DETACHED_SIGN.equals(action)) {
- return signImpl(data, input, output, false);
- } else if (OpenPgpApi.ACTION_ENCRYPT.equals(action)) {
- return encryptAndSignImpl(data, input, output, false);
- } else if (OpenPgpApi.ACTION_SIGN_AND_ENCRYPT.equals(action)) {
- return encryptAndSignImpl(data, input, output, true);
- } else if (OpenPgpApi.ACTION_DECRYPT_VERIFY.equals(action)) {
- return decryptAndVerifyImpl(data, input, output, false);
- } else if (OpenPgpApi.ACTION_DECRYPT_METADATA.equals(action)) {
- return decryptAndVerifyImpl(data, input, output, true);
- } else if (OpenPgpApi.ACTION_GET_SIGN_KEY_ID.equals(action)) {
- return getSignKeyIdImpl(data);
- } else if (OpenPgpApi.ACTION_GET_KEY_IDS.equals(action)) {
- return getKeyIdsImpl(data);
- } else if (OpenPgpApi.ACTION_GET_KEY.equals(action)) {
- return getKeyImpl(data);
- } else {
- return null;
+ switch (action) {
+ case OpenPgpApi.ACTION_CLEARTEXT_SIGN: {
+ return signImpl(data, input, output, true);
+ }
+ case OpenPgpApi.ACTION_SIGN: {
+ // DEPRECATED: same as ACTION_CLEARTEXT_SIGN
+ Log.w(Constants.TAG, "You are using a deprecated API call, please use ACTION_CLEARTEXT_SIGN instead of ACTION_SIGN!");
+ return signImpl(data, input, output, true);
+ }
+ case OpenPgpApi.ACTION_DETACHED_SIGN: {
+ return signImpl(data, input, output, false);
+ }
+ case OpenPgpApi.ACTION_ENCRYPT: {
+ return encryptAndSignImpl(data, input, output, false);
+ }
+ case OpenPgpApi.ACTION_SIGN_AND_ENCRYPT: {
+ return encryptAndSignImpl(data, input, output, true);
+ }
+ case OpenPgpApi.ACTION_DECRYPT_VERIFY: {
+ return decryptAndVerifyImpl(data, input, output, false);
+ }
+ case OpenPgpApi.ACTION_DECRYPT_METADATA: {
+ return decryptAndVerifyImpl(data, input, output, true);
+ }
+ case OpenPgpApi.ACTION_GET_SIGN_KEY_ID: {
+ return getSignKeyIdImpl(data);
+ }
+ case OpenPgpApi.ACTION_GET_KEY_IDS: {
+ return getKeyIdsImpl(data);
+ }
+ case OpenPgpApi.ACTION_GET_KEY: {
+ return getKeyImpl(data);
+ }
+ default: {
+ return null;
+ }
}
} finally {
// always close input and output file descriptors even in error cases
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java
index 507d4dea5..e19757d65 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java
@@ -31,7 +31,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.LogTyp
import org.sufficientlysecure.keychain.operations.results.SingletonResult;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.remote.AccountSettings;
-import org.sufficientlysecure.keychain.ui.BaseActivity;
+import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.util.Log;
public class AccountSettingsActivity extends BaseActivity {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java
index 407480c98..2b71d6dc1 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java
@@ -40,9 +40,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.remote.AppSettings;
-import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
-import org.sufficientlysecure.keychain.ui.BaseActivity;
-import org.sufficientlysecure.keychain.ui.dialog.AddSubkeyDialogFragment;
+import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.ui.dialog.AdvancedAppSettingsDialogFragment;
import org.sufficientlysecure.keychain.util.Log;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java
index e8c3e4511..f312c0d44 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java
@@ -38,7 +38,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.remote.AccountSettings;
import org.sufficientlysecure.keychain.remote.AppSettings;
-import org.sufficientlysecure.keychain.ui.BaseActivity;
+import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.ui.SelectPublicKeyFragment;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.Log;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java
index 98a44466d..cb9f46f7f 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java
@@ -29,7 +29,7 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.KeyRing;
-import org.sufficientlysecure.keychain.ui.BaseActivity;
+import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.ui.CreateKeyActivity;
import org.sufficientlysecure.keychain.util.Log;
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 57a11de21..a7571a7ac 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/CertifyActionsParcel.java
@@ -22,10 +22,14 @@ 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;
/**
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java
index e210536fd..63ea6285c 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java
@@ -46,7 +46,6 @@ 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.EditKeyResult;
import org.sufficientlysecure.keychain.operations.results.ExportResult;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.operations.results.CertifyResult;
@@ -68,6 +67,7 @@ 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;
@@ -159,8 +159,6 @@ public class KeychainIntentService extends IntentService implements Progressable
// decrypt/verify
public static final String DECRYPT_CIPHERTEXT_BYTES = "ciphertext_bytes";
- public static final String DECRYPT_PASSPHRASE = "passphrase";
- public static final String DECRYPT_NFC_DECRYPTED_SESSION_KEY = "nfc_decrypted_session_key";
// keybase proof
public static final String KEYBASE_REQUIRED_FINGERPRINT = "keybase_required_fingerprint";
@@ -169,6 +167,7 @@ public class KeychainIntentService extends IntentService implements Progressable
// 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";
@@ -193,7 +192,7 @@ public class KeychainIntentService extends IntentService implements Progressable
// promote key
public static final String PROMOTE_MASTER_KEY_ID = "promote_master_key_id";
- public static final String PROMOTE_TYPE = "promote_type";
+ public static final String PROMOTE_CARD_AID = "promote_card_aid";
// consolidate
public static final String CONSOLIDATE_RECOVERY = "consolidate_recovery";
@@ -260,11 +259,12 @@ public class KeychainIntentService extends IntentService implements Progressable
// 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, keyServerUri);
+ CertifyResult result = op.certify(parcel, cryptoInput, keyServerUri);
// Result
sendMessageToHandler(MessageStatus.OKAY, result);
@@ -289,27 +289,20 @@ public class KeychainIntentService extends IntentService implements Progressable
case ACTION_DECRYPT_METADATA: {
try {
- /* Input */
- Passphrase passphrase = data.getParcelable(DECRYPT_PASSPHRASE);
- byte[] nfcDecryptedSessionKey = data.getByteArray(DECRYPT_NFC_DECRYPTED_SESSION_KEY);
+ /* Input */
+ CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
InputData inputData = createDecryptInputData(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, null
);
builder.setAllowSymmetricDecryption(true)
- .setPassphrase(passphrase)
- .setDecryptMetadataOnly(true)
- .setNfcState(nfcDecryptedSessionKey);
+ .setDecryptMetadataOnly(true);
- DecryptVerifyResult decryptVerifyResult = builder.build().execute();
+ DecryptVerifyResult decryptVerifyResult = builder.build().execute(cryptoInput);
sendMessageToHandler(MessageStatus.OKAY, decryptVerifyResult);
} catch (Exception e) {
@@ -384,7 +377,8 @@ public class KeychainIntentService extends IntentService implements Progressable
);
builder.setSignedLiteralData(true).setRequiredSignerFingerprint(requiredFingerprint);
- DecryptVerifyResult decryptVerifyResult = builder.build().execute();
+ DecryptVerifyResult decryptVerifyResult = builder.build().execute(
+ new CryptoInputParcel());
outStream.close();
if (!decryptVerifyResult.success()) {
@@ -419,15 +413,13 @@ public class KeychainIntentService extends IntentService implements Progressable
case ACTION_DECRYPT_VERIFY: {
try {
- /* Input */
- Passphrase passphrase = data.getParcelable(DECRYPT_PASSPHRASE);
- byte[] nfcDecryptedSessionKey = data.getByteArray(DECRYPT_NFC_DECRYPTED_SESSION_KEY);
+ /* Input */
+ CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
InputData inputData = createDecryptInputData(data);
OutputStream outStream = createCryptOutputStream(data);
- /* Operation */
-
+ /* Operation */
Bundle resultData = new Bundle();
// verifyText and decrypt returning additional resultData values for the
@@ -436,24 +428,22 @@ public class KeychainIntentService extends IntentService implements Progressable
this, new ProviderHelper(this), this,
inputData, outStream
);
- builder.setAllowSymmetricDecryption(true)
- .setPassphrase(passphrase)
- .setNfcState(nfcDecryptedSessionKey);
+ builder.setAllowSymmetricDecryption(true);
- DecryptVerifyResult decryptVerifyResult = builder.build().execute();
+ DecryptVerifyResult decryptVerifyResult = builder.build().execute(cryptoInput);
outStream.close();
resultData.putParcelable(DecryptVerifyResult.EXTRA_RESULT, decryptVerifyResult);
- /* Output */
-
+ /* Output */
finalizeDecryptOutputStream(data, resultData, outStream);
-
Log.logDebugBundle(resultData, "resultData");
sendMessageToHandler(MessageStatus.OKAY, resultData);
- } catch (Exception e) {
+
+ } catch (IOException | PgpGeneralException e) {
+ // TODO get rid of this!
sendErrorToHandler(e);
}
@@ -478,11 +468,11 @@ public class KeychainIntentService extends IntentService implements Progressable
// Input
SaveKeyringParcel saveParcel = data.getParcelable(EDIT_KEYRING_PARCEL);
- Passphrase passphrase = data.getParcelable(EDIT_KEYRING_PASSPHRASE);
+ CryptoInputParcel cryptoInput = data.getParcelable(EXTRA_CRYPTO_INPUT);
// Operation
EditKeyOperation op = new EditKeyOperation(this, providerHelper, this, mActionCanceled);
- EditKeyResult result = op.execute(saveParcel, passphrase);
+ OperationResult result = op.execute(saveParcel, cryptoInput);
// Result
sendMessageToHandler(MessageStatus.OKAY, result);
@@ -492,11 +482,12 @@ public class KeychainIntentService extends IntentService implements Progressable
case ACTION_PROMOTE_KEYRING: {
// Input
- long keyRingId = data.getInt(EXPORT_KEY_RING_MASTER_KEY_ID);
+ 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);
+ PromoteKeyResult result = op.execute(keyRingId, cardAid);
// Result
sendMessageToHandler(MessageStatus.OKAY, result);
@@ -553,11 +544,12 @@ public class KeychainIntentService extends IntentService implements Progressable
// 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);
+ SignEncryptResult result = op.execute(inputParcel, cryptoInput);
// Result
sendMessageToHandler(MessageStatus.OKAY, result);
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 93a2bee23..78137170d 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java
@@ -60,18 +60,18 @@ import java.util.Date;
*
* Caching behavior for subkeys depends on the cacheSubs preference:
*
- * - If cacheSubs is NOT set, passphrases will be cached and retrieved by master key id. The
- * checks for special subkeys will still be done, but otherwise it is assumed that all subkeys
- * from the same master key will use the same passphrase. This can lead to bad passphrase
- * errors if two subkeys are encrypted differently. This is the default behavior.
+ * - If cacheSubs is NOT set, passphrases will be cached and retrieved by master key id. The
+ * checks for special subkeys will still be done, but otherwise it is assumed that all subkeys
+ * from the same master key will use the same passphrase. This can lead to bad passphrase
+ * errors if two subkeys are encrypted differently. This is the default behavior.
*
- * - If cacheSubs IS set, passphrases will be cached per subkey id. This means that if a keyring
- * has two subkeys for different purposes, passphrases will be cached independently and the
- * user will be asked for a passphrase once per subkey even if it is the same one. This mode
- * of operation is more precise, since we can assume that all passphrases returned from cache
- * will be correct without fail. Since keyrings with differently encrypted subkeys are a very
- * rare occurrence, and caching by keyring is what the user expects in the vast majority of
- * cases, this is not the default behavior.
+ * - If cacheSubs IS set, passphrases will be cached per subkey id. This means that if a keyring
+ * has two subkeys for different purposes, passphrases will be cached independently and the
+ * user will be asked for a passphrase once per subkey even if it is the same one. This mode
+ * of operation is more precise, since we can assume that all passphrases returned from cache
+ * will be correct without fail. Since keyrings with differently encrypted subkeys are a very
+ * rare occurrence, and caching by keyring is what the user expects in the vast majority of
+ * cases, this is not the default behavior.
*
*/
public class PassphraseCacheService extends Service {
@@ -123,7 +123,7 @@ public class PassphraseCacheService extends Service {
public static void addCachedPassphrase(Context context, long masterKeyId, long subKeyId,
Passphrase passphrase,
String primaryUserId) {
- Log.d(Constants.TAG, "PassphraseCacheService.cacheNewPassphrase() for " + masterKeyId);
+ Log.d(Constants.TAG, "PassphraseCacheService.addCachedPassphrase() for " + masterKeyId);
Intent intent = new Intent(context, PassphraseCacheService.class);
intent.setAction(ACTION_PASSPHRASE_CACHE_ADD);
@@ -137,10 +137,23 @@ public class PassphraseCacheService extends Service {
context.startService(intent);
}
+ public static void clearCachedPassphrase(Context context, long masterKeyId, long subKeyId) {
+ Log.d(Constants.TAG, "PassphraseCacheService.clearCachedPassphrase() for " + masterKeyId);
+
+ Intent intent = new Intent(context, PassphraseCacheService.class);
+ intent.setAction(ACTION_PASSPHRASE_CACHE_CLEAR);
+
+ intent.putExtra(EXTRA_KEY_ID, masterKeyId);
+ intent.putExtra(EXTRA_SUBKEY_ID, subKeyId);
+
+ context.startService(intent);
+ }
+
+
/**
* Gets a cached passphrase from memory by sending an intent to the service. This method is
* designed to wait until the service returns the passphrase.
-
+ *
* @return passphrase or null (if no passphrase is cached for this keyId)
*/
public static Passphrase getCachedPassphrase(Context context, long masterKeyId, long subKeyId) throws KeyNotFoundException {
@@ -218,7 +231,7 @@ public class PassphraseCacheService extends Service {
}
// on "none" key, just do nothing
- if(masterKeyId == Constants.key.none) {
+ if (masterKeyId == Constants.key.none) {
return null;
}
@@ -232,11 +245,11 @@ public class PassphraseCacheService extends Service {
switch (keyType) {
case DIVERT_TO_CARD:
- if (Preferences.getPreferences(this).useDefaultYubikeyPin()) {
- Log.d(Constants.TAG, "PassphraseCacheService: Using default Yubikey PIN: 123456");
- return new Passphrase("123456"); // default Yubikey PIN, see http://www.yubico.com/2012/12/yubikey-neo-openpgp/
+ if (Preferences.getPreferences(this).useDefaultYubiKeyPin()) {
+ Log.d(Constants.TAG, "PassphraseCacheService: Using default YubiKey PIN: 123456");
+ return new Passphrase("123456"); // default YubiKey PIN, see http://www.yubico.com/2012/12/yubikey-neo-openpgp/
} else {
- Log.d(Constants.TAG, "PassphraseCacheService: NOT using default Yubikey PIN");
+ Log.d(Constants.TAG, "PassphraseCacheService: NOT using default YubiKey PIN");
break;
}
case PASSPHRASE_EMPTY:
@@ -310,11 +323,11 @@ public class PassphraseCacheService extends Service {
/**
* Build pending intent that is executed by alarm manager to time out a specific passphrase
*/
- private static PendingIntent buildIntent(Context context, long keyId) {
+ private static PendingIntent buildIntent(Context context, long referenceKeyId) {
Intent intent = new Intent(BROADCAST_ACTION_PASSPHRASE_CACHE_SERVICE);
- intent.putExtra(EXTRA_KEY_ID, keyId);
+ intent.putExtra(EXTRA_KEY_ID, referenceKeyId);
// request code should be unique for each PendingIntent, thus keyId is used
- return PendingIntent.getBroadcast(context, (int) keyId, intent,
+ return PendingIntent.getBroadcast(context, (int) referenceKeyId, intent,
PendingIntent.FLAG_CANCEL_CURRENT);
}
@@ -325,11 +338,17 @@ public class PassphraseCacheService extends Service {
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(Constants.TAG, "PassphraseCacheService.onStartCommand()");
+ if (intent == null || intent.getAction() == null) {
+ updateService();
+ return START_STICKY;
+ }
+
// register broadcastreceiver
registerReceiver();
- if (intent != null && intent.getAction() != null) {
- if (ACTION_PASSPHRASE_CACHE_ADD.equals(intent.getAction())) {
+ String action = intent.getAction();
+ switch (action) {
+ case ACTION_PASSPHRASE_CACHE_ADD: {
long ttl = intent.getLongExtra(EXTRA_TTL, DEFAULT_TTL);
long masterKeyId = intent.getLongExtra(EXTRA_KEY_ID, -1);
long subKeyId = intent.getLongExtra(EXTRA_SUBKEY_ID, -1);
@@ -343,28 +362,19 @@ public class PassphraseCacheService extends Service {
);
// if we don't cache by specific subkey id, or the requested subkey is the master key,
- // just add master key id to the cache
- if (subKeyId == masterKeyId || !Preferences.getPreferences(mContext).getPassphraseCacheSubs()) {
- mPassphraseCache.put(masterKeyId, new CachedPassphrase(passphrase, primaryUserID));
- if (ttl > 0) {
- // register new alarm with keyId for this passphrase
- long triggerTime = new Date().getTime() + (ttl * 1000);
- AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
- am.set(AlarmManager.RTC_WAKEUP, triggerTime, buildIntent(this, masterKeyId));
- }
- } else {
- // otherwise, add this specific subkey to the cache
- mPassphraseCache.put(subKeyId, new CachedPassphrase(passphrase, primaryUserID));
- if (ttl > 0) {
- // register new alarm with keyId for this passphrase
- long triggerTime = new Date().getTime() + (ttl * 1000);
- AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
- am.set(AlarmManager.RTC_WAKEUP, triggerTime, buildIntent(this, subKeyId));
- }
+ // just add master key id to the cache, otherwise, add this specific subkey to the cache
+ long referenceKeyId =
+ Preferences.getPreferences(mContext).getPassphraseCacheSubs() ? subKeyId : masterKeyId;
+ mPassphraseCache.put(referenceKeyId, new CachedPassphrase(passphrase, primaryUserID));
+ if (ttl > 0) {
+ // register new alarm with keyId for this passphrase
+ long triggerTime = new Date().getTime() + (ttl * 1000);
+ AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
+ am.set(AlarmManager.RTC_WAKEUP, triggerTime, buildIntent(this, referenceKeyId));
}
-
- updateService();
- } else if (ACTION_PASSPHRASE_CACHE_GET.equals(intent.getAction())) {
+ break;
+ }
+ case ACTION_PASSPHRASE_CACHE_GET: {
long masterKeyId = intent.getLongExtra(EXTRA_KEY_ID, Constants.key.symmetric);
long subKeyId = intent.getLongExtra(EXTRA_SUBKEY_ID, Constants.key.symmetric);
Messenger messenger = intent.getParcelableExtra(EXTRA_MESSENGER);
@@ -392,22 +402,42 @@ public class PassphraseCacheService extends Service {
} catch (RemoteException e) {
Log.e(Constants.TAG, "PassphraseCacheService: Sending message failed", e);
}
- } else if (ACTION_PASSPHRASE_CACHE_CLEAR.equals(intent.getAction())) {
+ break;
+ }
+ case ACTION_PASSPHRASE_CACHE_CLEAR: {
AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
- // Stop all ttl alarms
- for (int i = 0; i < mPassphraseCache.size(); i++) {
- am.cancel(buildIntent(this, mPassphraseCache.keyAt(i)));
- }
+ if (intent.hasExtra(EXTRA_SUBKEY_ID) && intent.hasExtra(EXTRA_KEY_ID)) {
- mPassphraseCache.clear();
+ long referenceKeyId;
+ if (Preferences.getPreferences(mContext).getPassphraseCacheSubs()) {
+ referenceKeyId = intent.getLongExtra(EXTRA_KEY_ID, 0L);
+ } else {
+ referenceKeyId = intent.getLongExtra(EXTRA_SUBKEY_ID, 0L);
+ }
+ // Stop specific ttl alarm and
+ am.cancel(buildIntent(this, referenceKeyId));
+ mPassphraseCache.delete(referenceKeyId);
- updateService();
- } else {
+ } else {
+
+ // Stop all ttl alarms
+ for (int i = 0; i < mPassphraseCache.size(); i++) {
+ am.cancel(buildIntent(this, mPassphraseCache.keyAt(i)));
+ }
+ mPassphraseCache.clear();
+
+ }
+ break;
+ }
+ default: {
Log.e(Constants.TAG, "PassphraseCacheService: Intent or Intent Action not supported!");
+ break;
}
}
+ updateService();
+
return START_STICKY;
}
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 9fd278c13..2e0524141 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java
@@ -82,10 +82,14 @@ public class SaveKeyringParcel implements Parcelable {
mRevokeSubKeys = new ArrayList<>();
}
+ public boolean isEmpty() {
+ return isRestrictedOnly() && mChangeSubKeys.isEmpty();
+ }
+
/** Returns true iff this parcel does not contain any operations which require a passphrase. */
public boolean isRestrictedOnly() {
if (mNewUnlock != null || !mAddUserIds.isEmpty() || !mAddUserAttribute.isEmpty()
- || !mAddSubKeys.isEmpty() || mChangePrimaryUserId != null || !mRevokeSubKeys .isEmpty()
+ || !mAddSubKeys.isEmpty() || mChangePrimaryUserId != null || !mRevokeUserIds.isEmpty()
|| !mRevokeSubKeys.isEmpty()) {
return false;
}
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 4bd3481e6..430d8a49b 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ServiceProgressHandler.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ServiceProgressHandler.java
@@ -18,6 +18,7 @@
package org.sufficientlysecure.keychain.service;
import android.app.Activity;
+import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
@@ -26,6 +27,7 @@ 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;
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
new file mode 100644
index 000000000..3d1ccaca1
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2015 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.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.Passphrase;
+
+/**
+ * This is a base class for the input of crypto operations.
+ */
+public class CryptoInputParcel implements Parcelable {
+
+ final Date mSignatureTime;
+ final Passphrase mPassphrase;
+
+ // 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();
+ mPassphrase = null;
+ }
+
+ public CryptoInputParcel(Date signatureTime, Passphrase passphrase) {
+ mSignatureTime = signatureTime == null ? new Date() : signatureTime;
+ mPassphrase = passphrase;
+ }
+
+ public CryptoInputParcel(Passphrase passphrase) {
+ mSignatureTime = new Date();
+ mPassphrase = passphrase;
+ }
+
+ public CryptoInputParcel(Date signatureTime) {
+ mSignatureTime = signatureTime == null ? new Date() : signatureTime;
+ mPassphrase = null;
+ }
+
+ protected CryptoInputParcel(Parcel source) {
+ mSignatureTime = new Date(source.readLong());
+ mPassphrase = source.readParcelable(getClass().getClassLoader());
+
+ {
+ int count = source.readInt();
+ mCryptoData = new HashMap<>(count);
+ for (int i = 0; i < count; i++) {
+ byte[] key = source.createByteArray();
+ byte[] value = source.createByteArray();
+ mCryptoData.put(ByteBuffer.wrap(key), value);
+ }
+ }
+
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(mSignatureTime.getTime());
+ dest.writeParcelable(mPassphrase, 0);
+
+ dest.writeInt(mCryptoData.size());
+ for (HashMap.Entry<ByteBuffer, byte[]> entry : mCryptoData.entrySet()) {
+ dest.writeByteArray(entry.getKey().array());
+ dest.writeByteArray(entry.getValue());
+ }
+ }
+
+ public void addCryptoData(byte[] hash, byte[] signedHash) {
+ mCryptoData.put(ByteBuffer.wrap(hash), signedHash);
+ }
+
+ public Map<ByteBuffer, byte[]> getCryptoData() {
+ return Collections.unmodifiableMap(mCryptoData);
+ }
+
+ public Date getSignatureTime() {
+ return mSignatureTime;
+ }
+
+ public boolean hasPassphrase() {
+ return mPassphrase != null;
+ }
+
+ public Passphrase getPassphrase() {
+ return mPassphrase;
+ }
+
+ public static final Creator<CryptoInputParcel> CREATOR = new Creator<CryptoInputParcel>() {
+ public CryptoInputParcel createFromParcel(final Parcel source) {
+ return new CryptoInputParcel(source);
+ }
+
+ public CryptoInputParcel[] newArray(final int size) {
+ return new CryptoInputParcel[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder();
+ b.append("CryptoInput: { ");
+ b.append(mSignatureTime).append(" ");
+ if (mPassphrase != null) {
+ b.append("passphrase");
+ }
+ if (mCryptoData != null) {
+ b.append(mCryptoData.size());
+ b.append(" hashes ");
+ }
+ 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
new file mode 100644
index 000000000..535c1e735
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java
@@ -0,0 +1,214 @@
+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;
+
+
+public class RequiredInputParcel implements Parcelable {
+
+ public enum RequiredInputType {
+ PASSPHRASE, PASSPHRASE_SYMMETRIC, NFC_SIGN, NFC_DECRYPT
+ }
+
+ public Date mSignatureTime;
+
+ public final RequiredInputType mType;
+
+ public final byte[][] mInputHashes;
+ public final int[] mSignAlgos;
+
+ private Long mMasterKeyId;
+ private Long mSubKeyId;
+
+ private RequiredInputParcel(RequiredInputType type, byte[][] inputHashes,
+ int[] signAlgos, Date signatureTime, Long masterKeyId, Long subKeyId) {
+ mType = type;
+ mInputHashes = inputHashes;
+ mSignAlgos = signAlgos;
+ mSignatureTime = signatureTime;
+ mMasterKeyId = masterKeyId;
+ mSubKeyId = subKeyId;
+ }
+
+ public RequiredInputParcel(Parcel source) {
+ mType = RequiredInputType.values()[source.readInt()];
+
+ // 0 = none, 1 = both, 2 = only hashes (decrypt)
+ int hashTypes = source.readInt();
+ if (hashTypes != 0) {
+ int count = source.readInt();
+ mInputHashes = new byte[count][];
+ if (hashTypes == 1) {
+ mSignAlgos = new int[count];
+ for (int i = 0; i < count; i++) {
+ mInputHashes[i] = source.createByteArray();
+ mSignAlgos[i] = source.readInt();
+ }
+ } else {
+ mSignAlgos = null;
+ for (int i = 0; i < count; i++) {
+ mInputHashes[i] = source.createByteArray();
+ }
+ }
+ } else {
+ mInputHashes = null;
+ mSignAlgos = null;
+ }
+
+ mSignatureTime = source.readInt() != 0 ? new Date(source.readLong()) : null;
+ mMasterKeyId = source.readInt() != 0 ? source.readLong() : null;
+ mSubKeyId = source.readInt() != 0 ? source.readLong() : null;
+
+ }
+
+ public Long getMasterKeyId() {
+ return mMasterKeyId;
+ }
+
+ public Long getSubKeyId() {
+ return mSubKeyId;
+ }
+
+ public static RequiredInputParcel createNfcSignOperation(
+ byte[] inputHash, int signAlgo, Date signatureTime) {
+ return new RequiredInputParcel(RequiredInputType.NFC_SIGN,
+ new byte[][] { inputHash }, new int[] { signAlgo },
+ signatureTime, null, null);
+ }
+
+ public static RequiredInputParcel createNfcDecryptOperation(byte[] inputHash, long subKeyId) {
+ return new RequiredInputParcel(RequiredInputType.NFC_DECRYPT,
+ new byte[][] { inputHash }, null, null, null, subKeyId);
+ }
+
+ public static RequiredInputParcel createRequiredSignPassphrase(
+ long masterKeyId, long subKeyId, Date signatureTime) {
+ return new RequiredInputParcel(RequiredInputType.PASSPHRASE,
+ null, null, signatureTime, masterKeyId, subKeyId);
+ }
+
+ public static RequiredInputParcel createRequiredDecryptPassphrase(
+ long masterKeyId, long subKeyId) {
+ return new RequiredInputParcel(RequiredInputType.PASSPHRASE,
+ null, null, null, masterKeyId, subKeyId);
+ }
+
+ public static RequiredInputParcel createRequiredSymmetricPassphrase() {
+ return new RequiredInputParcel(RequiredInputType.PASSPHRASE_SYMMETRIC,
+ null, null, null, null, null);
+ }
+
+ public static RequiredInputParcel createRequiredPassphrase(
+ RequiredInputParcel req) {
+ return new RequiredInputParcel(RequiredInputType.PASSPHRASE,
+ null, null, req.mSignatureTime, req.mMasterKeyId, req.mSubKeyId);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mType.ordinal());
+ if (mInputHashes != null) {
+ dest.writeInt(mSignAlgos != null ? 1 : 2);
+ dest.writeInt(mInputHashes.length);
+ for (int i = 0; i < mInputHashes.length; i++) {
+ dest.writeByteArray(mInputHashes[i]);
+ if (mSignAlgos != null) {
+ dest.writeInt(mSignAlgos[i]);
+ }
+ }
+ } else {
+ dest.writeInt(0);
+ }
+ if (mSignatureTime != null) {
+ dest.writeInt(1);
+ dest.writeLong(mSignatureTime.getTime());
+ } else {
+ dest.writeInt(0);
+ }
+ if (mMasterKeyId != null) {
+ dest.writeInt(1);
+ dest.writeLong(mMasterKeyId);
+ } else {
+ dest.writeInt(0);
+ }
+ if (mSubKeyId != null) {
+ dest.writeInt(1);
+ dest.writeLong(mSubKeyId);
+ } else {
+ dest.writeInt(0);
+ }
+
+ }
+
+ public static final Creator<RequiredInputParcel> CREATOR = new Creator<RequiredInputParcel>() {
+ public RequiredInputParcel createFromParcel(final Parcel source) {
+ return new RequiredInputParcel(source);
+ }
+
+ public RequiredInputParcel[] newArray(final int size) {
+ return new RequiredInputParcel[size];
+ }
+ };
+
+ public static class NfcSignOperationsBuilder {
+ Date mSignatureTime;
+ ArrayList<Integer> mSignAlgos = new ArrayList<>();
+ ArrayList<byte[]> mInputHashes = new ArrayList<>();
+ Long mMasterKeyId;
+ Long mSubKeyId;
+
+ public NfcSignOperationsBuilder(Date signatureTime, Long masterKeyId, Long subKeyId) {
+ mSignatureTime = signatureTime;
+ mMasterKeyId = masterKeyId;
+ mSubKeyId = subKeyId;
+ }
+
+ public RequiredInputParcel build() {
+ byte[][] inputHashes = new byte[mInputHashes.size()][];
+ mInputHashes.toArray(inputHashes);
+ int[] signAlgos = new int[mSignAlgos.size()];
+ for (int i = 0; i < mSignAlgos.size(); i++) {
+ signAlgos[i] = mSignAlgos.get(i);
+ }
+
+ return new RequiredInputParcel(RequiredInputType.NFC_SIGN,
+ inputHashes, signAlgos, mSignatureTime, mMasterKeyId, mSubKeyId);
+ }
+
+ public void addHash(byte[] hash, int algo) {
+ mInputHashes.add(hash);
+ mSignAlgos.add(algo);
+ }
+
+ public void addAll(RequiredInputParcel input) {
+ if (!mSignatureTime.equals(input.mSignatureTime)) {
+ throw new AssertionError("input times must match, this is a programming error!");
+ }
+ if (input.mType != RequiredInputType.NFC_SIGN) {
+ throw new AssertionError("operation types must match, this is a progrmming error!");
+ }
+
+ Collections.addAll(mInputHashes, input.mInputHashes);
+ for (int signAlgo : input.mSignAlgos) {
+ mSignAlgos.add(signAlgo);
+ }
+ }
+
+ public boolean isEmpty() {
+ return mInputHashes.isEmpty();
+ }
+
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintActivity.java
index b7c80c1ed..016ab5f3c 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintActivity.java
@@ -23,6 +23,7 @@ import android.view.View;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.util.Log;
public class CertifyFingerprintActivity extends BaseActivity {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java
index 1fb88b182..3845e07cb 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java
@@ -19,6 +19,8 @@
package org.sufficientlysecure.keychain.ui;
import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.ui.base.BaseActivity;
+
/**
* Signs the specified public key with the specified secret master key
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java
index 9b6e8d8f9..59623a610 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java
@@ -25,8 +25,6 @@ import android.database.Cursor;
import android.database.MatrixCursor;
import android.graphics.PorterDuff;
import android.net.Uri;
-import android.os.Build.VERSION;
-import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.Message;
import android.os.Messenger;
@@ -56,23 +54,21 @@ import org.sufficientlysecure.keychain.service.CertifyActionsParcel;
import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
-import org.sufficientlysecure.keychain.service.PassphraseCacheService;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.adapter.MultiUserIdsAdapter;
+import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner;
import org.sufficientlysecure.keychain.ui.widget.KeySpinner;
import org.sufficientlysecure.keychain.util.Log;
-import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.Preferences;
-import java.lang.reflect.Method;
import java.util.ArrayList;
-public class CertifyKeyFragment extends LoaderFragment
- implements LoaderManager.LoaderCallbacks<Cursor> {
- public static final int REQUEST_CODE_PASSPHRASE = 0x00008001;
+public class CertifyKeyFragment extends CryptoOperationFragment
+ implements LoaderManager.LoaderCallbacks<Cursor> {
private CheckBox mUploadKeyCheckbox;
ListView mUserIds;
@@ -102,9 +98,6 @@ public class CertifyKeyFragment extends LoaderFragment
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
- // Start out with a progress indicator.
- setContentShown(false);
-
mPubMasterKeyIds = getActivity().getIntent().getLongArrayExtra(CertifyKeyActivity.EXTRA_KEY_IDS);
if (mPubMasterKeyIds == null) {
Log.e(Constants.TAG, "List of key ids to certify missing!");
@@ -114,6 +107,7 @@ public class CertifyKeyFragment extends LoaderFragment
mPassthroughMessenger = getActivity().getIntent().getParcelableExtra(
KeychainIntentService.EXTRA_MESSENGER);
+ mPassthroughMessenger = null; // TODO remove, development hack
// preselect certify key id if given
long certifyKeyId = getActivity().getIntent().getLongExtra(CertifyKeyActivity.EXTRA_CERTIFY_KEY_ID, Constants.key.none);
@@ -143,9 +137,7 @@ public class CertifyKeyFragment extends LoaderFragment
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
- View root = super.onCreateView(inflater, superContainer, savedInstanceState);
-
- View view = inflater.inflate(R.layout.certify_key_fragment, getContainer());
+ View view = inflater.inflate(R.layout.certify_key_fragment, null);
mCertifyKeySpinner = (CertifyKeySpinner) view.findViewById(R.id.certify_key_spinner);
mUploadKeyCheckbox = (CheckBox) view.findViewById(R.id.sign_key_upload_checkbox);
@@ -173,7 +165,7 @@ public class CertifyKeyFragment extends LoaderFragment
Notify.create(getActivity(), getString(R.string.select_key_to_certify),
Notify.Style.ERROR).show();
} else {
- initiateCertifying();
+ cryptoOperation(new CryptoInputParcel());
}
}
});
@@ -183,7 +175,7 @@ public class CertifyKeyFragment extends LoaderFragment
mUploadKeyCheckbox.setChecked(false);
}
- return root;
+ return view;
}
@Override
@@ -222,17 +214,6 @@ public class CertifyKeyFragment extends LoaderFragment
}) {
@Override
public byte[] getBlob(int column) {
- // For some reason, getBlob was not implemented before ICS
- if (VERSION.SDK_INT < VERSION_CODES.ICE_CREAM_SANDWICH) {
- try {
- // haha, yes there is int.class
- Method m = MatrixCursor.class.getDeclaredMethod("get", new Class[]{int.class});
- m.setAccessible(true);
- return (byte[]) m.invoke(this, 1);
- } catch (Exception e) {
- throw new UnsupportedOperationException(e);
- }
- }
return super.getBlob(column);
}
};
@@ -307,7 +288,6 @@ public class CertifyKeyFragment extends LoaderFragment
}
mUserIdsAdapter.swapCursor(matrix);
- setContentShown(true, isResumed());
}
@Override
@@ -315,49 +295,8 @@ public class CertifyKeyFragment extends LoaderFragment
mUserIdsAdapter.swapCursor(null);
}
- /**
- * handles the UI bits of the signing process on the UI thread
- */
- private void initiateCertifying() {
- // get the user's passphrase for this key (if required)
- Passphrase passphrase;
- try {
- passphrase = PassphraseCacheService.getCachedPassphrase(getActivity(), mSignMasterKeyId, mSignMasterKeyId);
- } catch (PassphraseCacheService.KeyNotFoundException e) {
- Log.e(Constants.TAG, "Key not found!", e);
- getActivity().finish();
- return;
- }
- if (passphrase == null) {
- Intent intent = new Intent(getActivity(), PassphraseDialogActivity.class);
- intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, mSignMasterKeyId);
- startActivityForResult(intent, REQUEST_CODE_PASSPHRASE);
- // bail out; need to wait until the user has entered the passphrase before trying again
- } else {
- startCertifying();
- }
- }
-
@Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- switch (requestCode) {
- case REQUEST_CODE_PASSPHRASE: {
- if (resultCode == Activity.RESULT_OK && data != null) {
- startCertifying();
- }
- return;
- }
-
- default: {
- super.onActivityResult(requestCode, resultCode, data);
- }
- }
- }
-
- /**
- * kicks off the actual signing process on a background thread
- */
- private void startCertifying() {
+ protected void cryptoOperation(CryptoInputParcel cryptoInput) {
// Bail out if there is not at least one user id selected
ArrayList<CertifyAction> certifyActions = mUserIdsAdapter.getSelectedCertifyActions();
if (certifyActions.isEmpty()) {
@@ -372,6 +311,7 @@ public class CertifyKeyFragment extends LoaderFragment
CertifyActionsParcel parcel = new CertifyActionsParcel(mSignMasterKeyId);
parcel.mCertifyActions.addAll(certifyActions);
+ data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
data.putParcelable(KeychainIntentService.CERTIFY_PARCEL, parcel);
if (mUploadKeyCheckbox.isChecked()) {
String keyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver();
@@ -396,11 +336,17 @@ public class CertifyKeyFragment extends LoaderFragment
true,
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
public void handleMessage(Message message) {
- // handle messages by standard KeychainIntentServiceHandler first
+ // handle messages by KeychainIntentCryptoServiceHandler first
super.handleMessage(message);
+ // handle pending messages
+ if (handlePendingMessage(message)) {
+ return;
+ }
+
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
Bundle data = message.getData();
+
CertifyResult result = data.getParcelable(CertifyResult.EXTRA_RESULT);
Intent intent = new Intent();
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java
index ab76f693e..0b203614b 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java
@@ -17,17 +17,25 @@
package org.sufficientlysecure.keychain.ui;
+import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
-import android.view.View;
import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
+import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
+import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Passphrase;
+import java.io.IOException;
import java.util.ArrayList;
-public class CreateKeyActivity extends BaseActivity {
+public class CreateKeyActivity extends BaseNfcActivity {
public static final String EXTRA_NAME = "name";
public static final String EXTRA_EMAIL = "email";
@@ -35,6 +43,10 @@ public class CreateKeyActivity extends BaseActivity {
public static final String EXTRA_ADDITIONAL_EMAILS = "additional_emails";
public static final String EXTRA_PASSPHRASE = "passphrase";
+ public static final String EXTRA_NFC_USER_ID = "nfc_user_id";
+ public static final String EXTRA_NFC_AID = "nfc_aid";
+ public static final String EXTRA_NFC_FINGERPRINTS = "nfc_fingerprints";
+
public static final String FRAGMENT_TAG = "currentFragment";
String mName;
@@ -60,14 +72,29 @@ public class CreateKeyActivity extends BaseActivity {
mCurrentFragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG);
} else {
+
+ Intent intent = getIntent();
// Initialize members with default values for a new instance
- mName = getIntent().getStringExtra(EXTRA_NAME);
- mEmail = getIntent().getStringExtra(EXTRA_EMAIL);
- mFirstTime = getIntent().getBooleanExtra(EXTRA_FIRST_TIME, false);
+ mName = intent.getStringExtra(EXTRA_NAME);
+ mEmail = intent.getStringExtra(EXTRA_EMAIL);
+ mFirstTime = intent.getBooleanExtra(EXTRA_FIRST_TIME, false);
+
+ if (intent.hasExtra(EXTRA_NFC_FINGERPRINTS)) {
+ byte[] nfcFingerprints = intent.getByteArrayExtra(EXTRA_NFC_FINGERPRINTS);
+ String nfcUserId = intent.getStringExtra(EXTRA_NFC_USER_ID);
+ byte[] nfcAid = intent.getByteArrayExtra(EXTRA_NFC_AID);
+
+ Fragment frag2 = CreateKeyYubiImportFragment.createInstance(
+ nfcFingerprints, nfcAid, nfcUserId);
+ loadFragment(frag2, FragAction.START);
+
+ setTitle(R.string.title_import_keys);
+ return;
+ } else {
+ CreateKeyStartFragment frag = CreateKeyStartFragment.newInstance();
+ loadFragment(frag, FragAction.START);
+ }
- // Start with first fragment of wizard
- CreateKeyStartFragment frag = CreateKeyStartFragment.newInstance();
- loadFragment(frag, FragAction.START);
}
if (mFirstTime) {
@@ -80,6 +107,38 @@ public class CreateKeyActivity extends BaseActivity {
}
@Override
+ protected void onNfcPerform() throws IOException {
+ if (mCurrentFragment instanceof NfcListenerFragment) {
+ ((NfcListenerFragment) mCurrentFragment).onNfcPerform();
+ return;
+ }
+
+ byte[] scannedFingerprints = nfcGetFingerprints();
+ byte[] nfcAid = nfcGetAid();
+ String userId = nfcGetUserId();
+
+ try {
+ long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(scannedFingerprints);
+ CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing(masterKeyId);
+ ring.getMasterKeyId();
+
+ Intent intent = new Intent(this, ViewKeyActivity.class);
+ intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId));
+ intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, nfcAid);
+ intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, userId);
+ intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, scannedFingerprints);
+ startActivity(intent);
+ finish();
+
+ } catch (PgpKeyNotFoundException e) {
+ Fragment frag = CreateKeyYubiImportFragment.createInstance(
+ scannedFingerprints, nfcAid, userId);
+ loadFragment(frag, FragAction.TO_RIGHT);
+ }
+
+ }
+
+ @Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
@@ -125,8 +184,14 @@ public class CreateKeyActivity extends BaseActivity {
break;
}
+
// do it immediately!
getSupportFragmentManager().executePendingTransactions();
+
+ }
+
+ interface NfcListenerFragment {
+ public void onNfcPerform() throws IOException;
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java
index 7e2e1c31c..85e2f8e9d 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java
@@ -126,7 +126,7 @@ public class CreateKeyEmailFragment extends Fragment {
if (mAdditionalEmailModels == null) {
mAdditionalEmailModels = new ArrayList<>();
if (mCreateKeyActivity.mAdditionalEmails != null) {
- setAdditionalEmails(mCreateKeyActivity.mAdditionalEmails);
+ mEmailAdapter.addAll(mCreateKeyActivity.mAdditionalEmails);
}
}
@@ -209,12 +209,6 @@ public class CreateKeyEmailFragment extends Fragment {
return emails;
}
- private void setAdditionalEmails(ArrayList<String> emails) {
- for (String email : emails) {
- mAdditionalEmailModels.add(new EmailAdapter.ViewModel(email));
- }
- }
-
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
@@ -244,8 +238,7 @@ public class CreateKeyEmailFragment extends Fragment {
// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder
- public static class ViewHolder extends RecyclerView.ViewHolder {
- // each data item is just a string in this case
+ class ViewHolder extends RecyclerView.ViewHolder {
public TextView mTextView;
public ImageButton mDeleteButton;
@@ -289,7 +282,10 @@ public class CreateKeyEmailFragment extends Fragment {
// Replace the contents of a view (invoked by the layout manager)
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
- if (holder instanceof ViewHolder) {
+ if (holder instanceof FooterHolder) {
+ FooterHolder thisHolder = (FooterHolder) holder;
+ thisHolder.mAddButton.setOnClickListener(mFooterOnClickListener);
+ } else if (holder instanceof ViewHolder) {
ViewHolder thisHolder = (ViewHolder) holder;
// - get element from your dataset at this position
// - replace the contents of the view with that element
@@ -302,9 +298,6 @@ public class CreateKeyEmailFragment extends Fragment {
remove(model);
}
});
- } else if (holder instanceof FooterHolder) {
- FooterHolder thisHolder = (FooterHolder) holder;
- thisHolder.mAddButton.setOnClickListener(mFooterOnClickListener);
}
}
@@ -332,6 +325,12 @@ public class CreateKeyEmailFragment extends Fragment {
notifyItemInserted(mDataset.size() - 1);
}
+ private void addAll(ArrayList<String> emails) {
+ for (String email : emails) {
+ mDataset.add(new EmailAdapter.ViewModel(email));
+ }
+ }
+
public void remove(ViewModel model) {
int position = mDataset.indexOf(model);
mDataset.remove(position);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java
index 180a52a1c..3f56949f5 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java
@@ -78,7 +78,7 @@ public class CreateKeyStartFragment extends Fragment {
mCreateKey = view.findViewById(R.id.create_key_create_key_button);
mImportKey = view.findViewById(R.id.create_key_import_button);
-// mYubiKey = view.findViewById(R.id.create_key_yubikey_button);
+ mYubiKey = view.findViewById(R.id.create_key_yubikey_button);
mCancel = (TextView) view.findViewById(R.id.create_key_cancel);
if (mCreateKeyActivity.mFirstTime) {
@@ -95,6 +95,14 @@ public class CreateKeyStartFragment extends Fragment {
}
});
+ mYubiKey.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ CreateKeyYubiWaitFragment frag = new CreateKeyYubiWaitFragment();
+ mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT);
+ }
+ });
+
mImportKey.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiImportFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiImportFragment.java
new file mode 100644
index 000000000..1cd0aaf2f
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiImportFragment.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2014 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.ui;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.Messenger;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import org.spongycastle.util.encoders.Hex;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
+import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
+import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
+import org.sufficientlysecure.keychain.service.KeychainIntentService;
+import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
+import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
+import org.sufficientlysecure.keychain.ui.CreateKeyActivity.NfcListenerFragment;
+import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
+import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
+import org.sufficientlysecure.keychain.util.Preferences;
+
+
+public class CreateKeyYubiImportFragment extends Fragment implements NfcListenerFragment {
+
+ private static final String ARG_FINGERPRINT = "fingerprint";
+ public static final String ARG_AID = "aid";
+ public static final String ARG_USER_ID = "user_ids";
+
+ CreateKeyActivity mCreateKeyActivity;
+
+ private byte[] mNfcFingerprints;
+ private long mNfcMasterKeyId;
+ private byte[] mNfcAid;
+ private String mNfcUserId;
+ private String mNfcFingerprint;
+ private ImportKeysListFragment mListFragment;
+ private TextView vSerNo;
+ private TextView vUserId;
+
+ public static Fragment createInstance(byte[] scannedFingerprints, byte[] nfcAid, String userId) {
+
+ CreateKeyYubiImportFragment frag = new CreateKeyYubiImportFragment();
+
+ Bundle args = new Bundle();
+ args.putByteArray(ARG_FINGERPRINT, scannedFingerprints);
+ args.putByteArray(ARG_AID, nfcAid);
+ args.putString(ARG_USER_ID, userId);
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Bundle args = savedInstanceState != null ? savedInstanceState : getArguments();
+
+ mNfcFingerprints = args.getByteArray(ARG_FINGERPRINT);
+ mNfcAid = args.getByteArray(ARG_AID);
+ mNfcUserId = args.getString(ARG_USER_ID);
+
+ mNfcMasterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mNfcFingerprints);
+ mNfcFingerprint = KeyFormattingUtils.convertFingerprintToHex(mNfcFingerprints);
+
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.create_yubikey_import_fragment, container, false);
+
+ vSerNo = (TextView) view.findViewById(R.id.yubikey_serno);
+ vUserId = (TextView) view.findViewById(R.id.yubikey_userid);
+
+ {
+ View mBackButton = view.findViewById(R.id.create_key_back_button);
+ mBackButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (getFragmentManager().getBackStackEntryCount() == 0) {
+ getActivity().setResult(Activity.RESULT_CANCELED);
+ getActivity().finish();
+ } else {
+ mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT);
+ }
+ }
+ });
+
+ View mNextButton = view.findViewById(R.id.create_key_next_button);
+ mNextButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ importKey();
+ }
+ });
+ }
+
+ mListFragment = ImportKeysListFragment.newInstance(null, null, "0x" + mNfcFingerprint, true);
+
+ view.findViewById(R.id.button_search).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ refreshSearch();
+ }
+ });
+
+ setData();
+
+ getFragmentManager().beginTransaction()
+ .replace(R.id.yubikey_import_fragment, mListFragment, "yubikey_import")
+ .commit();
+
+ return view;
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle args) {
+ super.onSaveInstanceState(args);
+
+ args.putByteArray(ARG_FINGERPRINT, mNfcFingerprints);
+ args.putByteArray(ARG_AID, mNfcAid);
+ args.putString(ARG_USER_ID, mNfcUserId);
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ mCreateKeyActivity = (CreateKeyActivity) getActivity();
+ }
+
+ public void setData() {
+ String serno = Hex.toHexString(mNfcAid, 10, 4);
+ vSerNo.setText(getString(R.string.yubikey_serno, serno));
+
+ if (!mNfcUserId.isEmpty()) {
+ vUserId.setText(getString(R.string.yubikey_key_holder, mNfcUserId));
+ } else {
+ vUserId.setText(getString(R.string.yubikey_key_holder_unset));
+ }
+ }
+
+ public void refreshSearch() {
+ mListFragment.loadNew(new ImportKeysListFragment.CloudLoaderState("0x" + mNfcFingerprint,
+ Preferences.getPreferences(getActivity()).getCloudSearchPrefs()));
+ }
+
+ public void importKey() {
+
+ // Message is received after decrypting is done in KeychainIntentService
+ ServiceProgressHandler saveHandler = new ServiceProgressHandler(
+ getActivity(),
+ getString(R.string.progress_importing),
+ ProgressDialog.STYLE_HORIZONTAL,
+ ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT
+ ) {
+ public void handleMessage(Message message) {
+ // handle messages by standard KeychainIntentServiceHandler first
+ super.handleMessage(message);
+
+ if (message.arg1 == MessageStatus.OKAY.ordinal()) {
+ // get returned data bundle
+ Bundle returnData = message.getData();
+
+ ImportKeyResult result =
+ returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT);
+
+ if (!result.success()) {
+ result.createNotify(getActivity()).show();
+ return;
+ }
+
+ Intent intent = new Intent(getActivity(), ViewKeyActivity.class);
+ intent.setData(KeyRings.buildGenericKeyRingUri(mNfcMasterKeyId));
+ intent.putExtra(ViewKeyActivity.EXTRA_DISPLAY_RESULT, result);
+ intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, mNfcAid);
+ intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, mNfcUserId);
+ intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, mNfcFingerprints);
+ startActivity(intent);
+ getActivity().finish();
+
+ }
+
+ }
+ };
+
+ // Send all information needed to service to decrypt in other thread
+ Intent intent = new Intent(getActivity(), KeychainIntentService.class);
+
+ // fill values for this action
+ Bundle data = new Bundle();
+
+ intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING);
+
+ String hexFp = KeyFormattingUtils.convertFingerprintToHex(mNfcFingerprints);
+ ArrayList<ParcelableKeyRing> keyList = new ArrayList<>();
+ keyList.add(new ParcelableKeyRing(hexFp, null, null));
+ data.putParcelableArrayList(KeychainIntentService.IMPORT_KEY_LIST, keyList);
+
+ {
+ Preferences prefs = Preferences.getPreferences(getActivity());
+ Preferences.CloudSearchPrefs cloudPrefs =
+ new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver());
+ data.putString(KeychainIntentService.IMPORT_KEY_SERVER, cloudPrefs.keyserver);
+ }
+
+ intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
+
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(saveHandler);
+ intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
+
+ saveHandler.showProgressDialog(getActivity());
+
+ // start service with intent
+ getActivity().startService(intent);
+
+ }
+
+ @Override
+ public void onNfcPerform() throws IOException {
+
+ mNfcFingerprints = mCreateKeyActivity.nfcGetFingerprints();
+ mNfcAid = mCreateKeyActivity.nfcGetAid();
+ mNfcUserId = mCreateKeyActivity.nfcGetUserId();
+
+ mNfcMasterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mNfcFingerprints);
+ mNfcFingerprint = KeyFormattingUtils.convertFingerprintToHex(mNfcFingerprints);
+
+ setData();
+ refreshSearch();
+
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiWaitFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiWaitFragment.java
new file mode 100644
index 000000000..579dddf79
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiWaitFragment.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2014 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.ui;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
+
+
+public class CreateKeyYubiWaitFragment extends Fragment {
+
+ CreateKeyActivity mCreateKeyActivity;
+ View mBackButton;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.create_yubikey_wait_fragment, container, false);
+
+ mBackButton = view.findViewById(R.id.create_key_back_button);
+
+ mBackButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT);
+ }
+ });
+
+ return view;
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ mCreateKeyActivity = (CreateKeyActivity) getActivity();
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java
index 162b10eca..dce2386b5 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesActivity.java
@@ -21,12 +21,12 @@ import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
-import android.os.PersistableBundle;
import android.view.View;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.api.OpenKeychainIntents;
+import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.util.Log;
public class DecryptFilesActivity extends BaseActivity {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java
index c75e28145..766e65e8b 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFilesFragment.java
@@ -17,6 +17,7 @@
package org.sufficientlysecure.keychain.ui;
+import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
@@ -32,13 +33,13 @@ import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.TextView;
-import org.openintents.openpgp.util.OpenPgpApi;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentService.IOType;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.ui.util.Notify;
@@ -63,6 +64,8 @@ public class DecryptFilesFragment extends DecryptFragment {
private Uri mInputUri = null;
private Uri mOutputUri = null;
+ private String mCurrentCryptoOperation;
+
/**
* Creates new instance of this fragment
*/
@@ -90,9 +93,6 @@ public class DecryptFilesFragment extends DecryptFragment {
mDecryptButton = view.findViewById(R.id.decrypt_file_action_decrypt);
view.findViewById(R.id.decrypt_file_browse).setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
- // reset state
- mPassphrase = null;
- mNfcDecryptedSessionKey = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
FileHelper.openDocument(DecryptFilesFragment.this, "*/*", REQUEST_CODE_INPUT);
} else {
@@ -144,7 +144,7 @@ public class DecryptFilesFragment extends DecryptFragment {
return;
}
- decryptOriginalFilename();
+ startDecryptFilenames();
}
private String removeEncryptedAppend(String name) {
@@ -157,110 +157,45 @@ public class DecryptFilesFragment extends DecryptFragment {
}
private void askForOutputFilename(String originalFilename) {
- String targetName;
- if (!TextUtils.isEmpty(originalFilename)) {
- targetName = originalFilename;
- } else {
- targetName = removeEncryptedAppend(FileHelper.getFilename(getActivity(), mInputUri));
+ if (TextUtils.isEmpty(originalFilename)) {
+ originalFilename = removeEncryptedAppend(FileHelper.getFilename(getActivity(), mInputUri));
}
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
File file = new File(mInputUri.getPath());
File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR;
- File targetFile = new File(parentDir, targetName);
+ File targetFile = new File(parentDir, originalFilename);
FileHelper.saveFile(this, getString(R.string.title_decrypt_to_file),
getString(R.string.specify_file_to_decrypt_to), targetFile, REQUEST_CODE_OUTPUT);
} else {
- FileHelper.saveDocument(this, "*/*", targetName, REQUEST_CODE_OUTPUT);
+ FileHelper.saveDocument(this, "*/*", originalFilename, REQUEST_CODE_OUTPUT);
}
}
- private void decryptOriginalFilename() {
- Log.d(Constants.TAG, "decryptOriginalFilename");
-
- Intent intent = new Intent(getActivity(), KeychainIntentService.class);
-
- // fill values for this action
- Bundle data = new Bundle();
- intent.setAction(KeychainIntentService.ACTION_DECRYPT_METADATA);
-
- // data
- Log.d(Constants.TAG, "mInputUri=" + mInputUri + ", mOutputUri=" + mOutputUri);
-
- data.putInt(KeychainIntentService.SOURCE, IOType.URI.ordinal());
- data.putParcelable(KeychainIntentService.ENCRYPT_DECRYPT_INPUT_URI, mInputUri);
-
- data.putInt(KeychainIntentService.TARGET, IOType.URI.ordinal());
- data.putParcelable(KeychainIntentService.ENCRYPT_DECRYPT_OUTPUT_URI, mOutputUri);
-
- data.putParcelable(KeychainIntentService.DECRYPT_PASSPHRASE, mPassphrase);
- data.putByteArray(KeychainIntentService.DECRYPT_NFC_DECRYPTED_SESSION_KEY, mNfcDecryptedSessionKey);
-
- intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
-
- // Message is received after decrypting is done in KeychainIntentService
- ServiceProgressHandler saveHandler = new ServiceProgressHandler(
- getActivity(),
- getString(R.string.progress_decrypting),
- ProgressDialog.STYLE_HORIZONTAL,
- ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
- public void handleMessage(Message message) {
- // handle messages by standard KeychainIntentServiceHandler first
- super.handleMessage(message);
-
- if (message.arg1 == MessageStatus.OKAY.ordinal()) {
- // get returned data bundle
- Bundle returnData = message.getData();
-
- DecryptVerifyResult pgpResult =
- returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT);
-
- if (pgpResult.isPending()) {
- if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) ==
- DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) {
- startPassphraseDialog(pgpResult.getKeyIdPassphraseNeeded());
- } else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) ==
- DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) {
- startPassphraseDialog(Constants.key.symmetric);
- } else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_NFC) ==
- DecryptVerifyResult.RESULT_PENDING_NFC) {
- startNfcDecrypt(pgpResult.getNfcSubKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcEncryptedSessionKey());
- } else {
- throw new RuntimeException("Unhandled pending result!");
- }
- } else if (pgpResult.success()) {
- // go on...
- askForOutputFilename(pgpResult.getDecryptMetadata().getFilename());
- } else {
- pgpResult.createNotify(getActivity()).show();
- }
- }
- }
- };
-
- // Create a new Messenger for the communication back
- Messenger messenger = new Messenger(saveHandler);
- intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
-
- // show progress dialog
- saveHandler.showProgressDialog(getActivity());
+ private void startDecrypt() {
+ mCurrentCryptoOperation = KeychainIntentService.ACTION_DECRYPT_VERIFY;
+ cryptoOperation(new CryptoInputParcel());
+ }
- // start service with intent
- getActivity().startService(intent);
+ private void startDecryptFilenames() {
+ mCurrentCryptoOperation = KeychainIntentService.ACTION_DECRYPT_METADATA;
+ cryptoOperation(new CryptoInputParcel());
}
@Override
- protected void decryptStart() {
- Log.d(Constants.TAG, "decryptStart");
-
+ @SuppressLint("HandlerLeak")
+ protected void cryptoOperation(CryptoInputParcel cryptoInput) {
// Send all information needed to service to decrypt in other thread
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
// fill values for this action
Bundle data = new Bundle();
-
- intent.setAction(KeychainIntentService.ACTION_DECRYPT_VERIFY);
+ // use current operation, either decrypt metadata or decrypt payload
+ intent.setAction(mCurrentCryptoOperation);
// data
+ data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
+
Log.d(Constants.TAG, "mInputUri=" + mInputUri + ", mOutputUri=" + mOutputUri);
data.putInt(KeychainIntentService.SOURCE, IOType.URI.ordinal());
@@ -269,8 +204,7 @@ public class DecryptFilesFragment extends DecryptFragment {
data.putInt(KeychainIntentService.TARGET, IOType.URI.ordinal());
data.putParcelable(KeychainIntentService.ENCRYPT_DECRYPT_OUTPUT_URI, mOutputUri);
- data.putParcelable(KeychainIntentService.DECRYPT_PASSPHRASE, mPassphrase);
- data.putByteArray(KeychainIntentService.DECRYPT_NFC_DECRYPTED_SESSION_KEY, mNfcDecryptedSessionKey);
+ data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
@@ -280,10 +214,16 @@ public class DecryptFilesFragment extends DecryptFragment {
getString(R.string.progress_decrypting),
ProgressDialog.STYLE_HORIZONTAL,
ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
+ @Override
public void handleMessage(Message message) {
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
+ // handle pending messages
+ if (handlePendingMessage(message)) {
+ return;
+ }
+
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
// get returned data bundle
Bundle returnData = message.getData();
@@ -291,39 +231,39 @@ public class DecryptFilesFragment extends DecryptFragment {
DecryptVerifyResult pgpResult =
returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT);
- if (pgpResult.isPending()) {
- if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) ==
- DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) {
- startPassphraseDialog(pgpResult.getKeyIdPassphraseNeeded());
- } else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) ==
- DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) {
- startPassphraseDialog(Constants.key.symmetric);
- } else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_NFC) ==
- DecryptVerifyResult.RESULT_PENDING_NFC) {
- startNfcDecrypt(pgpResult.getNfcSubKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcEncryptedSessionKey());
- } else {
- throw new RuntimeException("Unhandled pending result!");
- }
- } else if (pgpResult.success()) {
-
- // display signature result in activity
- onResult(pgpResult);
-
- if (mDeleteAfter.isChecked()) {
- // Create and show dialog to delete original file
- DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(mInputUri);
- deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog");
- setInputUri(null);
- }
-
- /*
- // A future open after decryption feature
- if () {
- Intent viewFile = new Intent(Intent.ACTION_VIEW);
- viewFile.setInputData(mOutputUri);
- startActivity(viewFile);
+ if (pgpResult.success()) {
+
+ switch (mCurrentCryptoOperation) {
+ case KeychainIntentService.ACTION_DECRYPT_METADATA: {
+ askForOutputFilename(pgpResult.getDecryptMetadata().getFilename());
+ break;
+ }
+ case KeychainIntentService.ACTION_DECRYPT_VERIFY: {
+ // display signature result in activity
+ onResult(pgpResult);
+
+ if (mDeleteAfter.isChecked()) {
+ // Create and show dialog to delete original file
+ DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(mInputUri);
+ deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog");
+ setInputUri(null);
+ }
+
+ /*
+ // A future open after decryption feature
+ if () {
+ Intent viewFile = new Intent(Intent.ACTION_VIEW);
+ viewFile.setInputData(mOutputUri);
+ startActivity(viewFile);
+ }
+ */
+ break;
+ }
+ default: {
+ Log.e(Constants.TAG, "Bug: not supported operation!");
+ break;
+ }
}
- */
} else {
pgpResult.createNotify(getActivity()).show();
}
@@ -346,22 +286,6 @@ public class DecryptFilesFragment extends DecryptFragment {
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
- case REQUEST_CODE_PASSPHRASE: {
- if (resultCode == Activity.RESULT_OK && data != null) {
- mPassphrase = data.getParcelableExtra(PassphraseDialogActivity.MESSAGE_DATA_PASSPHRASE);
- decryptOriginalFilename();
- }
- return;
- }
-
- case REQUEST_CODE_NFC_DECRYPT: {
- if (resultCode == Activity.RESULT_OK && data != null) {
- mNfcDecryptedSessionKey = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_DECRYPTED_SESSION_KEY);
- decryptOriginalFilename();
- }
- return;
- }
-
case REQUEST_CODE_INPUT: {
if (resultCode == Activity.RESULT_OK && data != null) {
setInputUri(data.getData());
@@ -373,7 +297,7 @@ public class DecryptFilesFragment extends DecryptFragment {
// This happens after output file was selected, so start our operation
if (resultCode == Activity.RESULT_OK && data != null) {
mOutputUri = data.getData();
- decryptStart();
+ startDecrypt();
}
return;
}
@@ -383,4 +307,5 @@ public class DecryptFilesFragment extends DecryptFragment {
}
}
}
+
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java
index 63508e530..33209be86 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java
@@ -19,7 +19,6 @@ package org.sufficientlysecure.keychain.ui;
import android.content.Intent;
import android.os.Bundle;
-import android.support.v4.app.Fragment;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -30,16 +29,13 @@ import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract;
+import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
-import org.sufficientlysecure.keychain.util.Passphrase;
-public abstract class DecryptFragment extends Fragment {
+public abstract class DecryptFragment extends CryptoOperationFragment {
private static final int RESULT_CODE_LOOKUP_KEY = 0x00007006;
- public static final int REQUEST_CODE_PASSPHRASE = 0x00008001;
- public static final int REQUEST_CODE_NFC_DECRYPT = 0x00008002;
-
protected long mSignatureKeyId = 0;
protected LinearLayout mResultLayout;
@@ -56,11 +52,6 @@ public abstract class DecryptFragment extends Fragment {
protected TextView mSignatureEmail;
protected TextView mSignatureAction;
-
- // State
- protected Passphrase mPassphrase;
- protected byte[] mNfcDecryptedSessionKey;
-
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
@@ -95,25 +86,6 @@ public abstract class DecryptFragment extends Fragment {
startActivity(viewKeyIntent);
}
- protected void startPassphraseDialog(long subkeyId) {
- Intent intent = new Intent(getActivity(), PassphraseDialogActivity.class);
- intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, subkeyId);
- startActivityForResult(intent, REQUEST_CODE_PASSPHRASE);
- }
-
- protected void startNfcDecrypt(long subKeyId, Passphrase pin, byte[] encryptedSessionKey) {
- // build PendingIntent for Yubikey NFC operations
- Intent intent = new Intent(getActivity(), NfcActivity.class);
- intent.setAction(NfcActivity.ACTION_DECRYPT_SESSION_KEY);
- intent.putExtra(NfcActivity.EXTRA_DATA, new Intent()); // not used, only relevant to OpenPgpService
- intent.putExtra(NfcActivity.EXTRA_KEY_ID, subKeyId);
- intent.putExtra(NfcActivity.EXTRA_PIN, pin);
-
- intent.putExtra(NfcActivity.EXTRA_NFC_ENC_SESSION_KEY, encryptedSessionKey);
-
- startActivityForResult(intent, REQUEST_CODE_NFC_DECRYPT);
- }
-
/**
*
* @return returns false if signature is invalid, key is revoked or expired.
@@ -253,9 +225,4 @@ public abstract class DecryptFragment extends Fragment {
});
}
- /**
- * Should be overridden by MessageFragment and FileFragment to start actual decryption
- */
- protected abstract void decryptStart();
-
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextActivity.java
index bc2ec014a..728e3ba41 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextActivity.java
@@ -31,6 +31,7 @@ import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.operations.results.SingletonResult;
import org.sufficientlysecure.keychain.pgp.PgpHelper;
+import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.Log;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java
index f6e21937d..9c6c89c43 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java
@@ -17,7 +17,6 @@
package org.sufficientlysecure.keychain.ui;
-import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.Bundle;
@@ -30,7 +29,6 @@ import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
-import org.openintents.openpgp.util.OpenPgpApi;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
@@ -38,6 +36,7 @@ import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentService.IOType;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.Log;
@@ -51,10 +50,7 @@ public class DecryptTextFragment extends DecryptFragment {
// view
private LinearLayout mValidLayout;
private LinearLayout mInvalidLayout;
- private Button mInvalidButton;
private TextView mText;
- private View mShareButton;
- private View mCopyButton;
// model
private String mCiphertext;
@@ -81,23 +77,26 @@ public class DecryptTextFragment extends DecryptFragment {
View view = inflater.inflate(R.layout.decrypt_text_fragment, container, false);
mValidLayout = (LinearLayout) view.findViewById(R.id.decrypt_text_valid);
mInvalidLayout = (LinearLayout) view.findViewById(R.id.decrypt_text_invalid);
- mInvalidButton = (Button) view.findViewById(R.id.decrypt_text_invalid_button);
mText = (TextView) view.findViewById(R.id.decrypt_text_plaintext);
- mShareButton = view.findViewById(R.id.action_decrypt_share_plaintext);
- mCopyButton = view.findViewById(R.id.action_decrypt_copy_plaintext);
- mShareButton.setOnClickListener(new View.OnClickListener() {
+
+ View vShareButton = view.findViewById(R.id.action_decrypt_share_plaintext);
+ vShareButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(sendWithChooserExcludingEncrypt(mText.getText().toString()));
}
});
- mCopyButton.setOnClickListener(new View.OnClickListener() {
+
+ View vCopyButton = view.findViewById(R.id.action_decrypt_copy_plaintext);
+ vCopyButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
copyToClipboard(mText.getText().toString());
}
});
- mInvalidButton.setOnClickListener(new View.OnClickListener() {
+
+ Button vInvalidButton = (Button) view.findViewById(R.id.decrypt_text_invalid_button);
+ vInvalidButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mInvalidLayout.setVisibility(View.GONE);
@@ -143,14 +142,12 @@ public class DecryptTextFragment extends DecryptFragment {
String ciphertext = getArguments().getString(ARG_CIPHERTEXT);
if (ciphertext != null) {
mCiphertext = ciphertext;
- decryptStart();
+ cryptoOperation(new CryptoInputParcel());
}
}
@Override
- protected void decryptStart() {
- Log.d(Constants.TAG, "decryptStart");
-
+ protected void cryptoOperation(CryptoInputParcel cryptoInput) {
// Send all information needed to service to decrypt in other thread
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
@@ -160,10 +157,10 @@ public class DecryptTextFragment extends DecryptFragment {
intent.setAction(KeychainIntentService.ACTION_DECRYPT_VERIFY);
// data
+ data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
data.putInt(KeychainIntentService.TARGET, IOType.BYTES.ordinal());
data.putByteArray(KeychainIntentService.DECRYPT_CIPHERTEXT_BYTES, mCiphertext.getBytes());
- data.putParcelable(KeychainIntentService.DECRYPT_PASSPHRASE, mPassphrase);
- data.putByteArray(KeychainIntentService.DECRYPT_NFC_DECRYPTED_SESSION_KEY, mNfcDecryptedSessionKey);
+ data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
@@ -177,6 +174,11 @@ public class DecryptTextFragment extends DecryptFragment {
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
+ // handle pending messages
+ if (handlePendingMessage(message)) {
+ return;
+ }
+
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
// get returned data bundle
Bundle returnData = message.getData();
@@ -184,20 +186,7 @@ public class DecryptTextFragment extends DecryptFragment {
DecryptVerifyResult pgpResult =
returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT);
- if (pgpResult.isPending()) {
- if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) ==
- DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) {
- startPassphraseDialog(pgpResult.getKeyIdPassphraseNeeded());
- } else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) ==
- DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) {
- startPassphraseDialog(Constants.key.symmetric);
- } else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_NFC) ==
- DecryptVerifyResult.RESULT_PENDING_NFC) {
- startNfcDecrypt(pgpResult.getNfcSubKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcEncryptedSessionKey());
- } else {
- throw new RuntimeException("Unhandled pending result!");
- }
- } else if (pgpResult.success()) {
+ if (pgpResult.success()) {
byte[] decryptedMessage = returnData
.getByteArray(KeychainIntentService.RESULT_DECRYPTED_BYTES);
@@ -245,34 +234,4 @@ public class DecryptTextFragment extends DecryptFragment {
getActivity().startService(intent);
}
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- switch (requestCode) {
-
- case REQUEST_CODE_PASSPHRASE: {
- if (resultCode == Activity.RESULT_OK && data != null) {
- mPassphrase = data.getParcelableExtra(PassphraseDialogActivity.MESSAGE_DATA_PASSPHRASE);
- decryptStart();
- } else {
- getActivity().finish();
- }
- return;
- }
-
- case REQUEST_CODE_NFC_DECRYPT: {
- if (resultCode == Activity.RESULT_OK && data != null) {
- mNfcDecryptedSessionKey = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_DECRYPTED_SESSION_KEY);
- decryptStart();
- } else {
- getActivity().finish();
- }
- return;
- }
-
- default: {
- super.onActivityResult(requestCode, resultCode, data);
- }
- }
- }
-
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java
index 6dc2994cf..b607ba9f4 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java
@@ -23,6 +23,7 @@ import android.os.Bundle;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
+import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.util.Log;
public class EditKeyActivity extends BaseActivity {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java
index 1e4b506d4..897de8490 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java
@@ -51,27 +51,27 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
-import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.adapter.SubkeysAdapter;
import org.sufficientlysecure.keychain.ui.adapter.SubkeysAddedAdapter;
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAddedAdapter;
+import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
import org.sufficientlysecure.keychain.ui.dialog.*;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase;
-public class EditKeyFragment extends LoaderFragment implements
+
+public class EditKeyFragment extends CryptoOperationFragment implements
LoaderManager.LoaderCallbacks<Cursor> {
public static final String ARG_DATA_URI = "uri";
public static final String ARG_SAVE_KEYRING_PARCEL = "save_keyring_parcel";
- public static final int REQUEST_CODE_PASSPHRASE = 0x00008001;
-
private ListView mUserIdsList;
private ListView mSubkeysList;
private ListView mUserIdsAddedList;
@@ -96,7 +96,6 @@ public class EditKeyFragment extends LoaderFragment implements
private SaveKeyringParcel mSaveKeyringParcel;
private String mPrimaryUserId;
- private Passphrase mCurrentPassphrase;
/**
* Creates new instance of this fragment
@@ -125,8 +124,7 @@ public class EditKeyFragment extends LoaderFragment implements
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
- View root = super.onCreateView(inflater, superContainer, savedInstanceState);
- View view = inflater.inflate(R.layout.edit_key_fragment, getContainer());
+ View view = inflater.inflate(R.layout.edit_key_fragment, null);
mUserIdsList = (ListView) view.findViewById(R.id.edit_key_user_ids);
mSubkeysList = (ListView) view.findViewById(R.id.edit_key_keys);
@@ -136,7 +134,7 @@ public class EditKeyFragment extends LoaderFragment implements
mAddUserId = view.findViewById(R.id.edit_key_action_add_user_id);
mAddSubkey = view.findViewById(R.id.edit_key_action_add_key);
- return root;
+ return view;
}
@Override
@@ -151,7 +149,7 @@ public class EditKeyFragment extends LoaderFragment implements
if (mDataUri == null) {
returnKeyringParcel();
} else {
- saveInDatabase(mCurrentPassphrase);
+ cryptoOperation(new CryptoInputParcel());
}
}
}, new OnClickListener() {
@@ -181,18 +179,12 @@ public class EditKeyFragment extends LoaderFragment implements
private void loadSaveKeyringParcel(SaveKeyringParcel saveKeyringParcel) {
mSaveKeyringParcel = saveKeyringParcel;
mPrimaryUserId = saveKeyringParcel.mChangePrimaryUserId;
- if (saveKeyringParcel.mNewUnlock != null) {
- mCurrentPassphrase = saveKeyringParcel.mNewUnlock.mNewPassphrase;
- }
mUserIdsAddedAdapter = new UserIdsAddedAdapter(getActivity(), mSaveKeyringParcel.mAddUserIds, true);
mUserIdsAddedList.setAdapter(mUserIdsAddedAdapter);
mSubkeysAddedAdapter = new SubkeysAddedAdapter(getActivity(), mSaveKeyringParcel.mAddSubKeys, true);
mSubkeysAddedList.setAdapter(mSubkeysAddedAdapter);
-
- // show directly
- setContentShown(true);
}
private void loadData(Uri dataUri) {
@@ -212,9 +204,6 @@ public class EditKeyFragment extends LoaderFragment implements
case GNU_DUMMY:
finishWithError(LogType.MSG_EK_ERROR_DUMMY);
return;
- case DIVERT_TO_CARD:
- finishWithError(LogType.MSG_EK_ERROR_DIVERT);
- break;
}
mSaveKeyringParcel = new SaveKeyringParcel(masterKeyId, keyRing.getFingerprint());
@@ -225,24 +214,10 @@ public class EditKeyFragment extends LoaderFragment implements
return;
}
- try {
- mCurrentPassphrase = PassphraseCacheService.getCachedPassphrase(getActivity(),
- mSaveKeyringParcel.mMasterKeyId, mSaveKeyringParcel.mMasterKeyId);
- } catch (PassphraseCacheService.KeyNotFoundException e) {
- finishWithError(LogType.MSG_EK_ERROR_NOT_FOUND);
- return;
- }
-
- if (mCurrentPassphrase == null) {
- Intent intent = new Intent(getActivity(), PassphraseDialogActivity.class);
- intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, mSaveKeyringParcel.mMasterKeyId);
- startActivityForResult(intent, REQUEST_CODE_PASSPHRASE);
- } else {
- // Prepare the loaders. Either re-connect with an existing ones,
- // or start new ones.
- getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, EditKeyFragment.this);
- getLoaderManager().initLoader(LOADER_ID_SUBKEYS, null, EditKeyFragment.this);
- }
+ // Prepare the loaders. Either re-connect with an existing ones,
+ // or start new ones.
+ getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, EditKeyFragment.this);
+ getLoaderManager().initLoader(LOADER_ID_SUBKEYS, null, EditKeyFragment.this);
mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0, mSaveKeyringParcel);
mUserIdsList.setAdapter(mUserIdsAdapter);
@@ -258,28 +233,6 @@ public class EditKeyFragment extends LoaderFragment implements
mSubkeysAddedList.setAdapter(mSubkeysAddedAdapter);
}
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- switch (requestCode) {
- case REQUEST_CODE_PASSPHRASE: {
- if (resultCode == Activity.RESULT_OK && data != null) {
- mCurrentPassphrase = data.getParcelableExtra(PassphraseDialogActivity.MESSAGE_DATA_PASSPHRASE);
- // Prepare the loaders. Either re-connect with an existing ones,
- // or start new ones.
- getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, EditKeyFragment.this);
- getLoaderManager().initLoader(LOADER_ID_SUBKEYS, null, EditKeyFragment.this);
- } else {
- getActivity().finish();
- }
- return;
- }
-
- default: {
- super.onActivityResult(requestCode, resultCode, data);
- }
- }
- }
-
private void initView() {
mChangePassphrase.setOnClickListener(new View.OnClickListener() {
@Override
@@ -318,7 +271,6 @@ public class EditKeyFragment extends LoaderFragment implements
}
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
- setContentShown(false);
switch (id) {
case LOADER_ID_USER_IDS: {
@@ -351,7 +303,6 @@ public class EditKeyFragment extends LoaderFragment implements
break;
}
- setContentShown(true);
}
/**
@@ -393,7 +344,7 @@ public class EditKeyFragment extends LoaderFragment implements
Messenger messenger = new Messenger(returnHandler);
SetPassphraseDialogFragment setPassphraseDialog = SetPassphraseDialogFragment.newInstance(
- messenger, mCurrentPassphrase, R.string.title_change_passphrase);
+ messenger, R.string.title_change_passphrase);
setPassphraseDialog.show(getActivity().getSupportFragmentManager(), "setPassphraseDialog");
}
@@ -589,8 +540,11 @@ public class EditKeyFragment extends LoaderFragment implements
getActivity().finish();
}
- private void saveInDatabase(Passphrase passphrase) {
- Log.d(Constants.TAG, "mSaveKeyringParcel:\n" + mSaveKeyringParcel.toString());
+ @Override
+ protected void cryptoOperation(CryptoInputParcel cryptoInput) {
+
+ Log.d(Constants.TAG, "cryptoInput:\n" + cryptoInput);
+ Log.d(Constants.TAG, "mSaveKeyringParcel:\n" + mSaveKeyringParcel);
ServiceProgressHandler saveHandler = new ServiceProgressHandler(
getActivity(),
@@ -602,6 +556,10 @@ public class EditKeyFragment extends LoaderFragment implements
// handle messages by standard KeychainIntentServiceHandler first
super.handleMessage(message);
+ if (handlePendingMessage(message)) {
+ return;
+ }
+
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
// get returned data bundle
@@ -637,7 +595,7 @@ public class EditKeyFragment extends LoaderFragment implements
// fill values for this action
Bundle data = new Bundle();
- data.putParcelable(KeychainIntentService.EDIT_KEYRING_PASSPHRASE, passphrase);
+ data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
data.putParcelable(KeychainIntentService.EDIT_KEYRING_PARCEL, mSaveKeyringParcel);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java
deleted file mode 100644
index cd1028de4..000000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivity.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright (C) 2014 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.ui;
-
-import android.app.Activity;
-import android.app.ProgressDialog;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.Message;
-import android.os.Messenger;
-import android.view.View;
-
-import org.openintents.openpgp.util.OpenPgpApi;
-import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;
-import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
-import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
-import org.sufficientlysecure.keychain.service.KeychainIntentService;
-import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
-import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
-import org.sufficientlysecure.keychain.util.Passphrase;
-
-import java.util.Date;
-
-public abstract class EncryptActivity extends BaseActivity {
-
- public static final int REQUEST_CODE_PASSPHRASE = 0x00008001;
- public static final int REQUEST_CODE_NFC = 0x00008002;
-
- // For NFC data
- protected Passphrase mSigningKeyPassphrase = null;
- protected Date mNfcTimestamp = null;
- protected byte[] mNfcHash = null;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- setFullScreenDialogClose(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- setResult(Activity.RESULT_CANCELED);
- finish();
- }
- }, false);
- }
-
- protected void startPassphraseDialog(long subkeyId) {
- Intent intent = new Intent(this, PassphraseDialogActivity.class);
- intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, subkeyId);
- startActivityForResult(intent, REQUEST_CODE_PASSPHRASE);
- }
-
- protected void startNfcSign(long keyId, Passphrase pin, byte[] hashToSign, int hashAlgo) {
- // build PendingIntent for Yubikey NFC operations
- Intent intent = new Intent(this, NfcActivity.class);
- intent.setAction(NfcActivity.ACTION_SIGN_HASH);
-
- // pass params through to activity that it can be returned again later to repeat pgp operation
- intent.putExtra(NfcActivity.EXTRA_DATA, new Intent()); // not used, only relevant to OpenPgpService
- intent.putExtra(NfcActivity.EXTRA_KEY_ID, keyId);
- intent.putExtra(NfcActivity.EXTRA_PIN, pin);
- intent.putExtra(NfcActivity.EXTRA_NFC_HASH_TO_SIGN, hashToSign);
- intent.putExtra(NfcActivity.EXTRA_NFC_HASH_ALGO, hashAlgo);
-
- startActivityForResult(intent, REQUEST_CODE_NFC);
- }
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- switch (requestCode) {
- case REQUEST_CODE_PASSPHRASE: {
- if (resultCode == RESULT_OK && data != null) {
- mSigningKeyPassphrase = data.getParcelableExtra(PassphraseDialogActivity.MESSAGE_DATA_PASSPHRASE);
- startEncrypt();
- return;
- }
- break;
- }
-
- case REQUEST_CODE_NFC: {
- if (resultCode == RESULT_OK && data != null) {
- mNfcHash = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_SIGNED_HASH);
- startEncrypt();
- return;
- }
- break;
- }
-
- default: {
- super.onActivityResult(requestCode, resultCode, data);
- break;
- }
- }
- }
-
- public void startEncrypt() {
- if (!inputIsValid()) {
- // Notify was created by inputIsValid.
- return;
- }
-
- // Send all information needed to service to edit key in other thread
- Intent intent = new Intent(this, KeychainIntentService.class);
- intent.setAction(KeychainIntentService.ACTION_SIGN_ENCRYPT);
-
- Bundle data = new Bundle();
- data.putParcelable(KeychainIntentService.SIGN_ENCRYPT_PARCEL, createEncryptBundle());
- intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
-
- // Message is received after encrypting is done in KeychainIntentService
- ServiceProgressHandler serviceHandler = new ServiceProgressHandler(
- this,
- getString(R.string.progress_encrypting),
- ProgressDialog.STYLE_HORIZONTAL,
- ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
- public void handleMessage(Message message) {
- // handle messages by standard KeychainIntentServiceHandler first
- super.handleMessage(message);
-
- if (message.arg1 == MessageStatus.OKAY.ordinal()) {
- SignEncryptResult result =
- message.getData().getParcelable(SignEncryptResult.EXTRA_RESULT);
-
- PgpSignEncryptResult pgpResult = result.getPending();
-
- if (pgpResult != null && pgpResult.isPending()) {
- if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE) ==
- PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE) {
- startPassphraseDialog(pgpResult.getKeyIdPassphraseNeeded());
- } else if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_NFC) ==
- PgpSignEncryptResult.RESULT_PENDING_NFC) {
-
- mNfcTimestamp = pgpResult.getNfcTimestamp();
- startNfcSign(pgpResult.getNfcKeyId(), pgpResult.getNfcPassphrase(),
- pgpResult.getNfcHash(), pgpResult.getNfcAlgo());
- } else {
- throw new RuntimeException("Unhandled pending result!");
- }
- return;
- }
-
- if (result.success()) {
- onEncryptSuccess(result);
- } else {
- result.createNotify(EncryptActivity.this).show();
- }
-
- // no matter the result, reset parameters
- mSigningKeyPassphrase = null;
- mNfcHash = null;
- mNfcTimestamp = null;
- }
- }
- };
- // Create a new Messenger for the communication back
- Messenger messenger = new Messenger(serviceHandler);
- intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
-
- // show progress dialog
- serviceHandler.showProgressDialog(this);
-
- // start service with intent
- startService(intent);
- }
-
- protected abstract boolean inputIsValid();
-
- protected abstract void onEncryptSuccess(SignEncryptResult result);
-
- protected abstract SignEncryptParcel createEncryptBundle();
-
-}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivityInterface.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivityInterface.java
deleted file mode 100644
index 2a102c6c4..000000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptActivityInterface.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2014 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.ui;
-
-import android.net.Uri;
-
-import org.sufficientlysecure.keychain.util.Passphrase;
-
-import java.util.ArrayList;
-
-public interface EncryptActivityInterface {
-
- public interface UpdateListener {
- void onNotifyUpdate();
- }
-
- public boolean isUseArmor();
- public boolean isUseCompression();
- public boolean isEncryptFilenames();
- public boolean isHiddenRecipients();
-
- public long getSignatureKey();
- public long[] getEncryptionKeys();
- public String[] getEncryptionUsers();
- public void setSignatureKey(long signatureKey);
- public void setEncryptionKeys(long[] encryptionKeys);
- public void setEncryptionUsers(String[] encryptionUsers);
-
- public void setPassphrase(Passphrase passphrase);
-
- // ArrayList on purpose as only those are parcelable
- public ArrayList<Uri> getInputUris();
- public ArrayList<Uri> getOutputUris();
- public void setInputUris(ArrayList<Uri> uris);
- public void setOutputUris(ArrayList<Uri> uris);
-
- public String getMessage();
- public void setMessage(String message);
-
- /**
- * Call this to notify the UI for changes done on the array lists or arrays,
- * automatically called if setter is used
- */
- public void notifyUpdate();
-
- public void startEncrypt(boolean share);
-}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java
index a498d0763..a6fad8881 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java
@@ -37,10 +37,6 @@ import java.util.regex.Matcher;
public class EncryptDecryptOverviewFragment extends Fragment {
- View mEncryptFile;
- View mEncryptText;
- View mDecryptFile;
- View mDecryptFromClipboard;
View mClipboardIcon;
@Override
@@ -53,10 +49,10 @@ public class EncryptDecryptOverviewFragment extends Fragment {
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.encrypt_decrypt_overview_fragment, container, false);
- mEncryptFile = view.findViewById(R.id.encrypt_files);
- mEncryptText = view.findViewById(R.id.encrypt_text);
- mDecryptFile = view.findViewById(R.id.decrypt_files);
- mDecryptFromClipboard = view.findViewById(R.id.decrypt_from_clipboard);
+ View mEncryptFile = view.findViewById(R.id.encrypt_files);
+ View mEncryptText = view.findViewById(R.id.encrypt_text);
+ View mDecryptFile = view.findViewById(R.id.decrypt_files);
+ View mDecryptFromClipboard = view.findViewById(R.id.decrypt_from_clipboard);
mClipboardIcon = view.findViewById(R.id.clipboard_icon);
mEncryptFile.setOnClickListener(new View.OnClickListener() {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java
index fe9b05226..64e908b1a 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2012-2015 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
*
* This program is free software: you can redistribute it and/or modify
@@ -18,32 +18,25 @@
package org.sufficientlysecure.keychain.ui;
+import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
-import android.view.Menu;
-import android.view.MenuItem;
+import android.support.v4.app.FragmentTransaction;
+import android.view.View;
-import org.spongycastle.bcpg.CompressionAlgorithmTags;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.api.OpenKeychainIntents;
-import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
-import org.sufficientlysecure.keychain.pgp.KeyRing;
-import org.sufficientlysecure.keychain.pgp.PgpConstants;
-import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
-import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;
-import org.sufficientlysecure.keychain.ui.util.Notify;
-import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.util.Passphrase;
-import org.sufficientlysecure.keychain.util.ShareHelper;
import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Set;
-public class EncryptFilesActivity extends EncryptActivity implements EncryptActivityInterface {
+public class EncryptFilesActivity extends BaseActivity implements
+ EncryptModeAsymmetricFragment.IAsymmetric, EncryptModeSymmetricFragment.ISymmetric,
+ EncryptFilesFragment.IMode {
/* Intents */
public static final String ACTION_ENCRYPT_DATA = OpenKeychainIntents.ENCRYPT_DATA;
@@ -55,302 +48,22 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi
public static final String EXTRA_SIGNATURE_KEY_ID = Constants.EXTRA_PREFIX + "EXTRA_SIGNATURE_KEY_ID";
public static final String EXTRA_ENCRYPTION_KEY_IDS = Constants.EXTRA_PREFIX + "EXTRA_ENCRYPTION_IDS";
- // view
- private int mCurrentMode = MODE_ASYMMETRIC;
-
- // tabs
- private static final int MODE_ASYMMETRIC = 0;
- private static final int MODE_SYMMETRIC = 1;
-
- // model used by fragments
- private boolean mUseArmor = false;
- private boolean mUseCompression = true;
- private boolean mDeleteAfterEncrypt = false;
- private boolean mShareAfterEncrypt = false;
- private boolean mEncryptFilenames = true;
- private boolean mHiddenRecipients = false;
-
- private long mEncryptionKeyIds[] = null;
- private String mEncryptionUserIds[] = null;
- private long mSigningKeyId = Constants.key.none;
- private Passphrase mPassphrase = new Passphrase();
-
- private ArrayList<Uri> mInputUris;
- private ArrayList<Uri> mOutputUris;
- private String mMessage = "";
-
- public boolean isModeSymmetric() {
- return MODE_SYMMETRIC == mCurrentMode;
- }
-
- @Override
- public boolean isUseArmor() {
- return mUseArmor;
- }
-
- @Override
- public boolean isUseCompression() {
- return mUseCompression;
- }
-
- @Override
- public boolean isEncryptFilenames() {
- return mEncryptFilenames;
- }
-
- @Override
- public boolean isHiddenRecipients() {
- return mHiddenRecipients;
- }
-
- @Override
- public long getSignatureKey() {
- return mSigningKeyId;
- }
-
- @Override
- public long[] getEncryptionKeys() {
- return mEncryptionKeyIds;
- }
-
- @Override
- public String[] getEncryptionUsers() {
- return mEncryptionUserIds;
- }
-
- @Override
- public void setSignatureKey(long signatureKey) {
- mSigningKeyId = signatureKey;
- notifyUpdate();
- }
-
- @Override
- public void setEncryptionKeys(long[] encryptionKeys) {
- mEncryptionKeyIds = encryptionKeys;
- notifyUpdate();
- }
-
- @Override
- public void setEncryptionUsers(String[] encryptionUsers) {
- mEncryptionUserIds = encryptionUsers;
- notifyUpdate();
- }
-
- @Override
- public void setPassphrase(Passphrase passphrase) {
- mPassphrase = passphrase;
- }
-
- @Override
- public ArrayList<Uri> getInputUris() {
- if (mInputUris == null) mInputUris = new ArrayList<>();
- return mInputUris;
- }
-
- @Override
- public ArrayList<Uri> getOutputUris() {
- if (mOutputUris == null) mOutputUris = new ArrayList<>();
- return mOutputUris;
- }
-
- @Override
- public void setInputUris(ArrayList<Uri> uris) {
- mInputUris = uris;
- notifyUpdate();
- }
-
- @Override
- public void setOutputUris(ArrayList<Uri> uris) {
- mOutputUris = uris;
- notifyUpdate();
- }
-
- @Override
- public String getMessage() {
- return mMessage;
- }
-
- @Override
- public void setMessage(String message) {
- mMessage = message;
- }
-
- @Override
- public void notifyUpdate() {
- for (Fragment fragment : getSupportFragmentManager().getFragments()) {
- if (fragment instanceof EncryptActivityInterface.UpdateListener) {
- ((UpdateListener) fragment).onNotifyUpdate();
- }
- }
- }
-
- @Override
- public void startEncrypt(boolean share) {
- mShareAfterEncrypt = share;
- startEncrypt();
- }
-
- @Override
- public void onEncryptSuccess(final SignEncryptResult result) {
- if (mDeleteAfterEncrypt) {
- final Uri[] inputUris = mInputUris.toArray(new Uri[mInputUris.size()]);
- DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(inputUris);
- deleteFileDialog.setOnDeletedListener(new DeleteFileDialogFragment.OnDeletedListener() {
-
- @Override
- public void onDeleted() {
- if (mShareAfterEncrypt) {
- // Share encrypted message/file
- startActivity(sendWithChooserExcludingEncrypt());
- } else {
- // Save encrypted file
- result.createNotify(EncryptFilesActivity.this).show();
- }
- }
-
- });
- deleteFileDialog.show(getSupportFragmentManager(), "deleteDialog");
-
- mInputUris.clear();
- notifyUpdate();
- } else {
- if (mShareAfterEncrypt) {
- // Share encrypted message/file
- startActivity(sendWithChooserExcludingEncrypt());
- } else {
- // Save encrypted file
- result.createNotify(EncryptFilesActivity.this).show();
- }
- }
- }
-
- @Override
- protected SignEncryptParcel createEncryptBundle() {
- // fill values for this action
- SignEncryptParcel data = new SignEncryptParcel();
-
- data.addInputUris(mInputUris);
- data.addOutputUris(mOutputUris);
-
- if (mUseCompression) {
- data.setCompressionId(PgpConstants.sPreferredCompressionAlgorithms.get(0));
- } else {
- data.setCompressionId(CompressionAlgorithmTags.UNCOMPRESSED);
- }
- data.setHiddenRecipients(mHiddenRecipients);
- data.setEnableAsciiArmorOutput(mUseArmor);
- data.setSymmetricEncryptionAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED);
- data.setSignatureHashAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED);
-
- if (isModeSymmetric()) {
- Log.d(Constants.TAG, "Symmetric encryption enabled!");
- Passphrase passphrase = mPassphrase;
- if (passphrase.isEmpty()) {
- passphrase = null;
- }
- data.setSymmetricPassphrase(passphrase);
- } else {
- data.setEncryptionMasterKeyIds(mEncryptionKeyIds);
- data.setSignatureMasterKeyId(mSigningKeyId);
- data.setSignaturePassphrase(mSigningKeyPassphrase);
- data.setNfcState(mNfcHash, mNfcTimestamp);
- }
- return data;
- }
-
- /**
- * Create Intent Chooser but exclude OK's EncryptActivity.
- */
- private Intent sendWithChooserExcludingEncrypt() {
- Intent prototype = createSendIntent();
- String title = getString(R.string.title_share_file);
-
- // we don't want to encrypt the encrypted, no inception ;)
- String[] blacklist = new String[]{
- Constants.PACKAGE_NAME + ".ui.EncryptFileActivity",
- "org.thialfihar.android.apg.ui.EncryptActivity"
- };
-
- return new ShareHelper(this).createChooserExcluding(prototype, title, blacklist);
- }
-
- private Intent createSendIntent() {
- Intent sendIntent;
- // file
- if (mOutputUris.size() == 1) {
- sendIntent = new Intent(Intent.ACTION_SEND);
- sendIntent.putExtra(Intent.EXTRA_STREAM, mOutputUris.get(0));
- } else {
- sendIntent = new Intent(Intent.ACTION_SEND_MULTIPLE);
- sendIntent.putExtra(Intent.EXTRA_STREAM, mOutputUris);
- }
- sendIntent.setType(Constants.ENCRYPTED_FILES_MIME);
-
- if (!isModeSymmetric() && mEncryptionUserIds != null) {
- Set<String> users = new HashSet<>();
- for (String user : mEncryptionUserIds) {
- KeyRing.UserId userId = KeyRing.splitUserId(user);
- if (userId.email != null) {
- users.add(userId.email);
- }
- }
- sendIntent.putExtra(Intent.EXTRA_EMAIL, users.toArray(new String[users.size()]));
- }
- return sendIntent;
- }
-
- protected boolean inputIsValid() {
- // file checks
-
- if (mInputUris.isEmpty()) {
- Notify.create(this, R.string.no_file_selected, Notify.Style.ERROR)
- .show(getSupportFragmentManager().findFragmentById(R.id.encrypt_file_fragment));
- return false;
- } else if (mInputUris.size() > 1 && !mShareAfterEncrypt) {
- // This should be impossible...
- return false;
- } else if (mInputUris.size() != mOutputUris.size()) {
- // This as well
- return false;
- }
-
- if (isModeSymmetric()) {
- // symmetric encryption checks
-
- if (mPassphrase == null) {
- Notify.create(this, R.string.passphrases_do_not_match, Notify.Style.ERROR)
- .show(getSupportFragmentManager().findFragmentById(R.id.encrypt_file_fragment));
- return false;
- }
- if (mPassphrase.isEmpty()) {
- Notify.create(this, R.string.passphrase_must_not_be_empty, Notify.Style.ERROR)
- .show(getSupportFragmentManager().findFragmentById(R.id.encrypt_file_fragment));
- return false;
- }
-
- } else {
- // asymmetric encryption checks
-
- boolean gotEncryptionKeys = (mEncryptionKeyIds != null
- && mEncryptionKeyIds.length > 0);
-
- // Files must be encrypted, only text can be signed-only right now
- if (!gotEncryptionKeys) {
- Notify.create(this, R.string.select_encryption_key, Notify.Style.ERROR)
- .show(getSupportFragmentManager().findFragmentById(R.id.encrypt_file_fragment));
- return false;
- }
- }
- return true;
- }
+ Fragment mModeFragment;
+ EncryptFilesFragment mEncryptFragment;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ setFullScreenDialogClose(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ finish();
+ }
+ }, false);
+
// Handle intent actions
- handleActions(getIntent());
- updateModeFragment();
+ handleActions(getIntent(), savedInstanceState);
}
@Override
@@ -358,73 +71,10 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi
setContentView(R.layout.encrypt_files_activity);
}
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- getMenuInflater().inflate(R.menu.encrypt_file_activity, menu);
- return super.onCreateOptionsMenu(menu);
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- if (item.isCheckable()) {
- item.setChecked(!item.isChecked());
- }
- switch (item.getItemId()) {
- case R.id.check_use_symmetric: {
- mCurrentMode = item.isChecked() ? MODE_SYMMETRIC : MODE_ASYMMETRIC;
- updateModeFragment();
- notifyUpdate();
- break;
- }
- case R.id.check_use_armor: {
- mUseArmor = item.isChecked();
- notifyUpdate();
- break;
- }
- case R.id.check_delete_after_encrypt: {
- mDeleteAfterEncrypt = item.isChecked();
- notifyUpdate();
- break;
- }
- case R.id.check_enable_compression: {
- mUseCompression = item.isChecked();
- notifyUpdate();
- break;
- }
- case R.id.check_encrypt_filenames: {
- mEncryptFilenames = item.isChecked();
- notifyUpdate();
- break;
- }
-// case R.id.check_hidden_recipients: {
-// mHiddenRecipients = item.isChecked();
-// notifyUpdate();
-// break;
-// }
- default: {
- return super.onOptionsItemSelected(item);
- }
- }
- return true;
- }
-
- private void updateModeFragment() {
- getSupportFragmentManager().beginTransaction()
- .replace(R.id.encrypt_pager_mode,
- mCurrentMode == MODE_SYMMETRIC
- ? new EncryptSymmetricFragment()
- : new EncryptAsymmetricFragment()
- )
- .commitAllowingStateLoss();
- getSupportFragmentManager().executePendingTransactions();
- }
-
/**
* Handles all actions with this intent
- *
- * @param intent
*/
- private void handleActions(Intent intent) {
+ private void handleActions(Intent intent, Bundle savedInstanceState) {
String action = intent.getAction();
Bundle extras = intent.getExtras();
String type = intent.getType();
@@ -453,14 +103,56 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi
uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
}
- mUseArmor = extras.getBoolean(EXTRA_ASCII_ARMOR, false);
+ long mSigningKeyId = extras.getLong(EXTRA_SIGNATURE_KEY_ID);
+ long[] mEncryptionKeyIds = extras.getLongArray(EXTRA_ENCRYPTION_KEY_IDS);
+ boolean useArmor = extras.getBoolean(EXTRA_ASCII_ARMOR, false);
+
+ if (savedInstanceState == null) {
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+
+ mModeFragment = EncryptModeAsymmetricFragment.newInstance(mSigningKeyId, mEncryptionKeyIds);
+ transaction.replace(R.id.encrypt_mode_container, mModeFragment, "mode");
+
+ mEncryptFragment = EncryptFilesFragment.newInstance(uris, useArmor);
+ transaction.replace(R.id.encrypt_file_container, mEncryptFragment, "files");
+
+ transaction.commit();
+
+ getSupportFragmentManager().executePendingTransactions();
+ }
+ }
+
+ @Override
+ public void onModeChanged(boolean symmetric) {
+ // switch fragments
+ getSupportFragmentManager().beginTransaction()
+ .replace(R.id.encrypt_mode_container,
+ symmetric
+ ? EncryptModeSymmetricFragment.newInstance()
+ : EncryptModeAsymmetricFragment.newInstance(0, null)
+ )
+ .commitAllowingStateLoss();
+ getSupportFragmentManager().executePendingTransactions();
+ }
- // preselect keys given by intent
- mSigningKeyId = extras.getLong(EXTRA_SIGNATURE_KEY_ID);
- mEncryptionKeyIds = extras.getLongArray(EXTRA_ENCRYPTION_KEY_IDS);
+ @Override
+ public void onSignatureKeyIdChanged(long signatureKeyId) {
+ mEncryptFragment.setSigningKeyId(signatureKeyId);
+ }
+
+ @Override
+ public void onEncryptionKeyIdsChanged(long[] encryptionKeyIds) {
+ mEncryptFragment.setEncryptionKeyIds(encryptionKeyIds);
+ }
- // Save uris
- mInputUris = uris;
+ @Override
+ public void onEncryptionUserIdsChanged(String[] encryptionUserIds) {
+ mEncryptFragment.setEncryptionUserIds(encryptionUserIds);
+ }
+
+ @Override
+ public void onPassphraseChanged(Passphrase passphrase) {
+ mEncryptFragment.setPassphrase(passphrase);
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java
index 4ba76d8ea..ccb4a6355 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2014-2015 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
@@ -17,57 +17,130 @@
package org.sufficientlysecure.keychain.ui;
-import android.annotation.TargetApi;
import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
-import android.support.v4.app.Fragment;
+import android.os.Message;
+import android.os.Messenger;
+import android.support.v7.widget.DefaultItemAnimator;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.BaseAdapter;
+import android.widget.Button;
import android.widget.ImageView;
-import android.widget.ListView;
import android.widget.TextView;
+import org.spongycastle.bcpg.CompressionAlgorithmTags;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
+import org.sufficientlysecure.keychain.pgp.KeyRing;
+import org.sufficientlysecure.keychain.pgp.PgpConstants;
+import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
+import org.sufficientlysecure.keychain.service.KeychainIntentService;
+import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.ui.adapter.SpacesItemDecoration;
+import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
+import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;
+import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.FileHelper;
+import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.Passphrase;
+import org.sufficientlysecure.keychain.util.ShareHelper;
import java.io.File;
-import java.util.HashMap;
+import java.io.IOException;
+import java.util.ArrayList;
import java.util.HashSet;
-import java.util.Map;
+import java.util.List;
+import java.util.Set;
-public class EncryptFilesFragment extends Fragment implements EncryptActivityInterface.UpdateListener {
+public class EncryptFilesFragment extends CryptoOperationFragment {
+
+ public interface IMode {
+ public void onModeChanged(boolean symmetric);
+ }
+
+ public static final String ARG_USE_ASCII_ARMOR = "use_ascii_armor";
public static final String ARG_URIS = "uris";
private static final int REQUEST_CODE_INPUT = 0x00007003;
private static final int REQUEST_CODE_OUTPUT = 0x00007007;
- private EncryptActivityInterface mEncryptInterface;
+ private IMode mModeInterface;
+
+ private boolean mSymmetricMode = false;
+ private boolean mUseArmor = false;
+ private boolean mUseCompression = true;
+ private boolean mDeleteAfterEncrypt = false;
+ private boolean mShareAfterEncrypt = false;
+ private boolean mEncryptFilenames = true;
+ private boolean mHiddenRecipients = false;
+
+ private long mEncryptionKeyIds[] = null;
+ private String mEncryptionUserIds[] = null;
+ private long mSigningKeyId = Constants.key.none;
+ private Passphrase mPassphrase = new Passphrase();
+
+ private ArrayList<Uri> mOutputUris = new ArrayList<>();
+
+ private RecyclerView mSelectedFiles;
+
+ ArrayList<FilesAdapter.ViewModel> mFilesModels;
+ FilesAdapter mFilesAdapter;
+
+ /**
+ * Creates new instance of this fragment
+ */
+ public static EncryptFilesFragment newInstance(ArrayList<Uri> uris, boolean useArmor) {
+ EncryptFilesFragment frag = new EncryptFilesFragment();
+
+ Bundle args = new Bundle();
+ args.putBoolean(ARG_USE_ASCII_ARMOR, useArmor);
+ args.putParcelableArrayList(ARG_URIS, uris);
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ public void setEncryptionKeyIds(long[] encryptionKeyIds) {
+ mEncryptionKeyIds = encryptionKeyIds;
+ }
- // view
- private View mAddView;
- private ListView mSelectedFiles;
- private SelectedFilesAdapter mAdapter = new SelectedFilesAdapter();
- private final Map<Uri, Bitmap> thumbnailCache = new HashMap<>();
+ public void setEncryptionUserIds(String[] encryptionUserIds) {
+ mEncryptionUserIds = encryptionUserIds;
+ }
+
+ public void setSigningKeyId(long signingKeyId) {
+ mSigningKeyId = signingKeyId;
+ }
+
+ public void setPassphrase(Passphrase passphrase) {
+ mPassphrase = passphrase;
+ }
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
- mEncryptInterface = (EncryptActivityInterface) activity;
+ mModeInterface = (IMode) activity;
} catch (ClassCastException e) {
- throw new ClassCastException(activity.toString() + " must implement EncryptActivityInterface");
+ throw new ClassCastException(activity + " must be IMode");
}
}
@@ -77,17 +150,28 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.encrypt_files_fragment, container, false);
+ mSelectedFiles = (RecyclerView) view.findViewById(R.id.selected_files_list);
+
+ mSelectedFiles.addItemDecoration(new SpacesItemDecoration(
+ FormattingUtils.dpToPx(getActivity(), 4)));
+ mSelectedFiles.setHasFixedSize(true);
+ mSelectedFiles.setLayoutManager(new LinearLayoutManager(getActivity()));
+ mSelectedFiles.setItemAnimator(new DefaultItemAnimator());
- mAddView = inflater.inflate(R.layout.file_list_entry_add, null);
- mAddView.setOnClickListener(new View.OnClickListener() {
+ mFilesModels = new ArrayList<>();
+ mFilesAdapter = new FilesAdapter(getActivity(), mFilesModels, new View.OnClickListener() {
@Override
public void onClick(View v) {
addInputUri();
}
});
- mSelectedFiles = (ListView) view.findViewById(R.id.selected_files_list);
- mSelectedFiles.addFooterView(mAddView);
- mSelectedFiles.setAdapter(mAdapter);
+
+ ArrayList<Uri> inputUris = getArguments().getParcelableArrayList(ARG_URIS);
+ if (inputUris != null) {
+ mFilesAdapter.addAll(inputUris);
+ }
+ mUseArmor = getArguments().getBoolean(ARG_USE_ASCII_ARMOR);
+ mSelectedFiles.setAdapter(mFilesAdapter);
return view;
}
@@ -95,7 +179,6 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
-
setHasOptionsMenu(true);
}
@@ -103,8 +186,8 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
FileHelper.openDocument(EncryptFilesFragment.this, "*/*", true, REQUEST_CODE_INPUT);
} else {
- FileHelper.openFile(EncryptFilesFragment.this, mEncryptInterface.getInputUris().isEmpty() ?
- null : mEncryptInterface.getInputUris().get(mEncryptInterface.getInputUris().size() - 1),
+ FileHelper.openFile(EncryptFilesFragment.this, mFilesModels.isEmpty() ?
+ null : mFilesModels.get(mFilesModels.size() - 1).inputUri,
"*/*", REQUEST_CODE_INPUT);
}
}
@@ -114,34 +197,27 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt
return;
}
- if (mEncryptInterface.getInputUris().contains(inputUri)) {
+ try {
+ mFilesAdapter.add(inputUri);
+ } catch (IOException e) {
Notify.create(getActivity(),
getActivity().getString(R.string.error_file_added_already, FileHelper.getFilename(getActivity(), inputUri)),
- Notify.Style.ERROR).show(this);
+ Notify.Style.ERROR).show();
return;
}
-
- mEncryptInterface.getInputUris().add(inputUri);
- mEncryptInterface.notifyUpdate();
- mSelectedFiles.requestFocus();
- }
-
- private void delInputUri(int position) {
- mEncryptInterface.getInputUris().remove(position);
- mEncryptInterface.notifyUpdate();
mSelectedFiles.requestFocus();
}
private void showOutputFileDialog() {
- if (mEncryptInterface.getInputUris().size() > 1 || mEncryptInterface.getInputUris().isEmpty()) {
+ if (mFilesModels.size() > 1 || mFilesModels.isEmpty()) {
throw new IllegalStateException();
}
- Uri inputUri = mEncryptInterface.getInputUris().get(0);
+ FilesAdapter.ViewModel model = mFilesModels.get(0);
String targetName =
- (mEncryptInterface.isEncryptFilenames() ? "1" : FileHelper.getFilename(getActivity(), inputUri))
- + (mEncryptInterface.isUseArmor() ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN);
+ (mEncryptFilenames ? "1" : FileHelper.getFilename(getActivity(), model.inputUri))
+ + (mUseArmor ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
- File file = new File(inputUri.getPath());
+ File file = new File(model.inputUri.getPath());
File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR;
File targetFile = new File(parentDir, targetName);
FileHelper.saveFile(this, getString(R.string.title_encrypt_to_file),
@@ -152,44 +228,61 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt
}
private void encryptClicked(boolean share) {
- if (mEncryptInterface.getInputUris().isEmpty()) {
- Notify.create(getActivity(), R.string.error_no_file_selected, Notify.Style.ERROR).show(this);
+ if (mFilesModels.isEmpty()) {
+ Notify.create(getActivity(), R.string.error_no_file_selected,
+ Notify.Style.ERROR).show();
return;
}
if (share) {
- mEncryptInterface.getOutputUris().clear();
+ mOutputUris.clear();
int filenameCounter = 1;
- for (Uri uri : mEncryptInterface.getInputUris()) {
+ for (FilesAdapter.ViewModel model : mFilesModels) {
String targetName =
- (mEncryptInterface.isEncryptFilenames() ? String.valueOf(filenameCounter) : FileHelper.getFilename(getActivity(), uri))
- + (mEncryptInterface.isUseArmor() ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN);
- mEncryptInterface.getOutputUris().add(TemporaryStorageProvider.createFile(getActivity(), targetName));
+ (mEncryptFilenames ? String.valueOf(filenameCounter) : FileHelper.getFilename(getActivity(), model.inputUri))
+ + (mUseArmor ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN);
+ mOutputUris.add(TemporaryStorageProvider.createFile(getActivity(), targetName));
filenameCounter++;
}
- mEncryptInterface.startEncrypt(true);
+ startEncrypt(true);
} else {
- if (mEncryptInterface.getInputUris().size() > 1) {
- Notify.create(getActivity(), R.string.error_multi_not_supported, Notify.Style.ERROR).show(this);
+ if (mFilesModels.size() > 1) {
+ Notify.create(getActivity(), R.string.error_multi_not_supported,
+ Notify.Style.ERROR).show();
return;
}
showOutputFileDialog();
}
}
- @TargetApi(Build.VERSION_CODES.KITKAT)
- public boolean handleClipData(Intent data) {
- if (data.getClipData() != null && data.getClipData().getItemCount() > 0) {
- for (int i = 0; i < data.getClipData().getItemCount(); i++) {
- Uri uri = data.getClipData().getItemAt(i).getUri();
- if (uri != null) addInputUri(uri);
+ public void addFile(Intent data) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
+ addInputUri(data.getData());
+ } else {
+ if (data.getClipData() != null && data.getClipData().getItemCount() > 0) {
+ for (int i = 0; i < data.getClipData().getItemCount(); i++) {
+ Uri uri = data.getClipData().getItemAt(i).getUri();
+ if (uri != null) {
+ addInputUri(uri);
+ }
+ }
+ } else {
+ // fallback, try old method to get single uri
+ addInputUri(data.getData());
}
- return true;
}
- return false;
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ super.onCreateOptionsMenu(menu, inflater);
+ inflater.inflate(R.menu.encrypt_file_fragment, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.isCheckable()) {
+ item.setChecked(!item.isChecked());
+ }
switch (item.getItemId()) {
case R.id.encrypt_save: {
encryptClicked(false);
@@ -199,6 +292,32 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt
encryptClicked(true);
break;
}
+ case R.id.check_use_symmetric: {
+ mSymmetricMode = item.isChecked();
+ mModeInterface.onModeChanged(mSymmetricMode);
+ break;
+ }
+ case R.id.check_use_armor: {
+ mUseArmor = item.isChecked();
+ break;
+ }
+ case R.id.check_delete_after_encrypt: {
+ mDeleteAfterEncrypt = item.isChecked();
+ break;
+ }
+ case R.id.check_enable_compression: {
+ mUseCompression = item.isChecked();
+ break;
+ }
+ case R.id.check_encrypt_filenames: {
+ mEncryptFilenames = item.isChecked();
+ break;
+ }
+// case R.id.check_hidden_recipients: {
+// mHiddenRecipients = item.isChecked();
+// notifyUpdate();
+// break;
+// }
default: {
return super.onOptionsItemSelected(item);
}
@@ -206,24 +325,234 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt
return true;
}
+ protected boolean inputIsValid() {
+ // file checks
+
+ if (mFilesModels.isEmpty()) {
+ Notify.create(getActivity(), R.string.no_file_selected, Notify.Style.ERROR)
+ .show();
+ return false;
+ } else if (mFilesModels.size() > 1 && !mShareAfterEncrypt) {
+ Log.e(Constants.TAG, "Aborting: mInputUris.size() > 1 && !mShareAfterEncrypt");
+ // This should be impossible...
+ return false;
+ } else if (mFilesModels.size() != mOutputUris.size()) {
+ Log.e(Constants.TAG, "Aborting: mInputUris.size() != mOutputUris.size()");
+ // This as well
+ return false;
+ }
+
+ if (mSymmetricMode) {
+ // symmetric encryption checks
+
+ if (mPassphrase == null) {
+ Notify.create(getActivity(), R.string.passphrases_do_not_match, Notify.Style.ERROR)
+ .show();
+ return false;
+ }
+ if (mPassphrase.isEmpty()) {
+ Notify.create(getActivity(), R.string.passphrase_must_not_be_empty, Notify.Style.ERROR)
+ .show();
+ return false;
+ }
+
+ } else {
+ // asymmetric encryption checks
+
+ boolean gotEncryptionKeys = (mEncryptionKeyIds != null
+ && mEncryptionKeyIds.length > 0);
+
+ // Files must be encrypted, only text can be signed-only right now
+ if (!gotEncryptionKeys) {
+ Notify.create(getActivity(), R.string.select_encryption_key, Notify.Style.ERROR)
+ .show();
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public void onEncryptSuccess(final SignEncryptResult result) {
+ if (mDeleteAfterEncrypt) {
+ DeleteFileDialogFragment deleteFileDialog =
+ DeleteFileDialogFragment.newInstance(mFilesAdapter.getAsArrayList());
+ deleteFileDialog.setOnDeletedListener(new DeleteFileDialogFragment.OnDeletedListener() {
+
+ @Override
+ public void onDeleted() {
+ if (mShareAfterEncrypt) {
+ // Share encrypted message/file
+ startActivity(sendWithChooserExcludingEncrypt());
+ } else {
+ // Save encrypted file
+ result.createNotify(getActivity()).show();
+ }
+ }
+
+ });
+ deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog");
+ } else {
+ if (mShareAfterEncrypt) {
+ // Share encrypted message/file
+ startActivity(sendWithChooserExcludingEncrypt());
+ } else {
+ // Save encrypted file
+ result.createNotify(getActivity()).show();
+ }
+ }
+ }
+
+ protected SignEncryptParcel createEncryptBundle() {
+ // fill values for this action
+ SignEncryptParcel data = new SignEncryptParcel();
+
+ data.addInputUris(mFilesAdapter.getAsArrayList());
+ data.addOutputUris(mOutputUris);
+
+ if (mUseCompression) {
+ data.setCompressionId(PgpConstants.sPreferredCompressionAlgorithms.get(0));
+ } else {
+ data.setCompressionId(CompressionAlgorithmTags.UNCOMPRESSED);
+ }
+ data.setHiddenRecipients(mHiddenRecipients);
+ data.setEnableAsciiArmorOutput(mUseArmor);
+ data.setSymmetricEncryptionAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED);
+ data.setSignatureHashAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED);
+
+ if (mSymmetricMode) {
+ Log.d(Constants.TAG, "Symmetric encryption enabled!");
+ Passphrase passphrase = mPassphrase;
+ if (passphrase.isEmpty()) {
+ passphrase = null;
+ }
+ data.setSymmetricPassphrase(passphrase);
+ } else {
+ data.setEncryptionMasterKeyIds(mEncryptionKeyIds);
+ data.setSignatureMasterKeyId(mSigningKeyId);
+ }
+ return data;
+ }
+
+ /**
+ * Create Intent Chooser but exclude OK's EncryptActivity.
+ */
+ private Intent sendWithChooserExcludingEncrypt() {
+ Intent prototype = createSendIntent();
+ String title = getString(R.string.title_share_file);
+
+ // we don't want to encrypt the encrypted, no inception ;)
+ String[] blacklist = new String[]{
+ Constants.PACKAGE_NAME + ".ui.EncryptFileActivity",
+ "org.thialfihar.android.apg.ui.EncryptActivity"
+ };
+
+ return new ShareHelper(getActivity()).createChooserExcluding(prototype, title, blacklist);
+ }
+
+ private Intent createSendIntent() {
+ Intent sendIntent;
+ // file
+ if (mOutputUris.size() == 1) {
+ sendIntent = new Intent(Intent.ACTION_SEND);
+ sendIntent.putExtra(Intent.EXTRA_STREAM, mOutputUris.get(0));
+ } else {
+ sendIntent = new Intent(Intent.ACTION_SEND_MULTIPLE);
+ sendIntent.putExtra(Intent.EXTRA_STREAM, mOutputUris);
+ }
+ sendIntent.setType(Constants.ENCRYPTED_FILES_MIME);
+
+ if (!mSymmetricMode && mEncryptionUserIds != null) {
+ Set<String> users = new HashSet<>();
+ for (String user : mEncryptionUserIds) {
+ KeyRing.UserId userId = KeyRing.splitUserId(user);
+ if (userId.email != null) {
+ users.add(userId.email);
+ }
+ }
+ sendIntent.putExtra(Intent.EXTRA_EMAIL, users.toArray(new String[users.size()]));
+ }
+ return sendIntent;
+ }
+
+ public void startEncrypt(boolean share) {
+ mShareAfterEncrypt = share;
+ cryptoOperation();
+ }
+
+ @Override
+ protected void cryptoOperation(CryptoInputParcel cryptoInput) {
+
+ if (!inputIsValid()) {
+ // Notify was created by inputIsValid.
+ Log.d(Constants.TAG, "Input not valid!");
+ return;
+ }
+ Log.d(Constants.TAG, "Input valid!");
+
+ // Send all information needed to service to edit key in other thread
+ Intent intent = new Intent(getActivity(), KeychainIntentService.class);
+ intent.setAction(KeychainIntentService.ACTION_SIGN_ENCRYPT);
+
+ final SignEncryptParcel input = createEncryptBundle();
+
+ Bundle data = new Bundle();
+ data.putParcelable(KeychainIntentService.SIGN_ENCRYPT_PARCEL, input);
+ data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
+ intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
+
+ // Message is received after encrypting is done in KeychainIntentService
+ ServiceProgressHandler serviceHandler = new ServiceProgressHandler(
+ getActivity(),
+ getString(R.string.progress_encrypting),
+ ProgressDialog.STYLE_HORIZONTAL,
+ true,
+ ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
+ public void handleMessage(Message message) {
+ // handle messages by standard KeychainIntentServiceHandler first
+ super.handleMessage(message);
+
+ // handle pending messages
+ if (handlePendingMessage(message)) {
+ return;
+ }
+
+ if (message.arg1 == MessageStatus.OKAY.ordinal()) {
+ SignEncryptResult result =
+ message.getData().getParcelable(SignEncryptResult.EXTRA_RESULT);
+ if (result.success()) {
+ onEncryptSuccess(result);
+ } else {
+ result.createNotify(getActivity()).show();
+ }
+ }
+ }
+ };
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(serviceHandler);
+ intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
+
+ // show progress dialog
+ serviceHandler.showProgressDialog(getActivity());
+
+ // start service with intent
+ getActivity().startService(intent);
+ }
+
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_CODE_INPUT: {
if (resultCode == Activity.RESULT_OK && data != null) {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT || !handleClipData(data)) {
- addInputUri(data.getData());
- }
+ addFile(data);
}
return;
}
case REQUEST_CODE_OUTPUT: {
// This happens after output file was selected, so start our operation
if (resultCode == Activity.RESULT_OK && data != null) {
- mEncryptInterface.getOutputUris().clear();
- mEncryptInterface.getOutputUris().add(data.getData());
- mEncryptInterface.notifyUpdate();
- mEncryptInterface.startEncrypt(false);
+ mOutputUris.clear();
+ mOutputUris.add(data.getData());
+ startEncrypt(false);
}
return;
}
@@ -236,67 +565,190 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt
}
}
- @Override
- public void onNotifyUpdate() {
- // Clear cache if needed
- for (Uri uri : new HashSet<>(thumbnailCache.keySet())) {
- if (!mEncryptInterface.getInputUris().contains(uri)) {
- thumbnailCache.remove(uri);
+ public static class FilesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
+ private Activity mActivity;
+ private List<ViewModel> mDataset;
+ private View.OnClickListener mFooterOnClickListener;
+ private static final int TYPE_FOOTER = 0;
+ private static final int TYPE_ITEM = 1;
+
+ public static class ViewModel {
+ Uri inputUri;
+ Bitmap thumbnail;
+ String filename;
+ long fileSize;
+
+ ViewModel(Context context, Uri inputUri) {
+ this.inputUri = inputUri;
+ int px = FormattingUtils.dpToPx(context, 48);
+ this.thumbnail = FileHelper.getThumbnail(context, inputUri, new Point(px, px));
+ this.filename = FileHelper.getFilename(context, inputUri);
+ this.fileSize = FileHelper.getFileSize(context, inputUri);
+ }
+
+ /**
+ * Depends on inputUri only
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ViewModel viewModel = (ViewModel) o;
+ return !(inputUri != null ? !inputUri.equals(viewModel.inputUri)
+ : viewModel.inputUri != null);
+ }
+
+ /**
+ * Depends on inputUri only
+ */
+ @Override
+ public int hashCode() {
+ return inputUri != null ? inputUri.hashCode() : 0;
+ }
+
+ @Override
+ public String toString() {
+ return inputUri.toString();
}
}
- mAdapter.notifyDataSetChanged();
- }
+ // Provide a reference to the views for each data item
+ // Complex data items may need more than one view per item, and
+ // you provide access to all the views for a data item in a view holder
+ class ViewHolder extends RecyclerView.ViewHolder {
+ public TextView filename;
+ public TextView fileSize;
+ public View removeButton;
+ public ImageView thumbnail;
+
+ public ViewHolder(View itemView) {
+ super(itemView);
+ filename = (TextView) itemView.findViewById(R.id.filename);
+ fileSize = (TextView) itemView.findViewById(R.id.filesize);
+ removeButton = itemView.findViewById(R.id.action_remove_file_from_list);
+ thumbnail = (ImageView) itemView.findViewById(R.id.thumbnail);
+ }
+ }
- private class SelectedFilesAdapter extends BaseAdapter {
+ class FooterHolder extends RecyclerView.ViewHolder {
+ public Button mAddButton;
+
+ public FooterHolder(View itemView) {
+ super(itemView);
+ mAddButton = (Button) itemView.findViewById(R.id.file_list_entry_add);
+ }
+ }
+
+ // Provide a suitable constructor (depends on the kind of dataset)
+ public FilesAdapter(Activity activity, List<ViewModel> myDataset, View.OnClickListener onFooterClickListener) {
+ mActivity = activity;
+ mDataset = myDataset;
+ mFooterOnClickListener = onFooterClickListener;
+ }
+
+ // Create new views (invoked by the layout manager)
@Override
- public int getCount() {
- return mEncryptInterface.getInputUris().size();
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ if (viewType == TYPE_FOOTER) {
+ View v = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.file_list_entry_add, parent, false);
+ return new FooterHolder(v);
+ } else {
+ //inflate your layout and pass it to view holder
+ View v = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.file_list_entry, parent, false);
+ return new ViewHolder(v);
+ }
}
+ // Replace the contents of a view (invoked by the layout manager)
@Override
- public Object getItem(int position) {
- return mEncryptInterface.getInputUris().get(position);
+ public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
+ if (holder instanceof FooterHolder) {
+ FooterHolder thisHolder = (FooterHolder) holder;
+ thisHolder.mAddButton.setOnClickListener(mFooterOnClickListener);
+ } else if (holder instanceof ViewHolder) {
+ ViewHolder thisHolder = (ViewHolder) holder;
+ // - get element from your dataset at this position
+ // - replace the contents of the view with that element
+ final ViewModel model = mDataset.get(position);
+
+ thisHolder.filename.setText(model.filename);
+ if (model.fileSize == -1) {
+ thisHolder.fileSize.setText("");
+ } else {
+ thisHolder.fileSize.setText(FileHelper.readableFileSize(model.fileSize));
+ }
+ thisHolder.removeButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ remove(model);
+ }
+ });
+ if (model.thumbnail != null) {
+ thisHolder.thumbnail.setImageBitmap(model.thumbnail);
+ } else {
+ thisHolder.thumbnail.setImageResource(R.drawable.ic_doc_generic_am);
+ }
+ }
}
+ // Return the size of your dataset (invoked by the layout manager)
@Override
- public long getItemId(int position) {
- return getItem(position).hashCode();
+ public int getItemCount() {
+ return mDataset.size() + 1;
}
@Override
- public View getView(final int position, View convertView, ViewGroup parent) {
- Uri inputUri = mEncryptInterface.getInputUris().get(position);
- View view;
- if (convertView == null) {
- view = getActivity().getLayoutInflater().inflate(R.layout.file_list_entry, null);
+ public int getItemViewType(int position) {
+ if (isPositionFooter(position)) {
+ return TYPE_FOOTER;
} else {
- view = convertView;
+ return TYPE_ITEM;
}
- ((TextView) view.findViewById(R.id.filename)).setText(FileHelper.getFilename(getActivity(), inputUri));
- long size = FileHelper.getFileSize(getActivity(), inputUri);
- if (size == -1) {
- ((TextView) view.findViewById(R.id.filesize)).setText("");
- } else {
- ((TextView) view.findViewById(R.id.filesize)).setText(FileHelper.readableFileSize(size));
+ }
+
+ private boolean isPositionFooter(int position) {
+ return position == mDataset.size();
+ }
+
+ public void add(Uri inputUri) throws IOException {
+ ViewModel newModel = new ViewModel(mActivity, inputUri);
+ if (mDataset.contains(newModel)) {
+ throw new IOException("Already added!");
}
- view.findViewById(R.id.action_remove_file_from_list).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- delInputUri(position);
+ mDataset.add(newModel);
+ notifyItemInserted(mDataset.size() - 1);
+ }
+
+ public void addAll(ArrayList<Uri> inputUris) {
+ if (inputUris != null) {
+ int startIndex = mDataset.size();
+ for (Uri inputUri : inputUris) {
+ ViewModel newModel = new ViewModel(mActivity, inputUri);
+ if (mDataset.contains(newModel)) {
+ Log.e(Constants.TAG, "Skipped duplicate " + inputUri.toString());
+ } else {
+ mDataset.add(newModel);
+ }
}
- });
- int px = FormattingUtils.dpToPx(getActivity(), 48);
- if (!thumbnailCache.containsKey(inputUri)) {
- thumbnailCache.put(inputUri, FileHelper.getThumbnail(getActivity(), inputUri, new Point(px, px)));
+ notifyItemRangeInserted(startIndex, mDataset.size() - startIndex);
}
- Bitmap bitmap = thumbnailCache.get(inputUri);
- if (bitmap != null) {
- ((ImageView) view.findViewById(R.id.thumbnail)).setImageBitmap(bitmap);
- } else {
- ((ImageView) view.findViewById(R.id.thumbnail)).setImageResource(R.drawable.ic_doc_generic_am);
+ }
+
+ public void remove(ViewModel model) {
+ int position = mDataset.indexOf(model);
+ mDataset.remove(position);
+ notifyItemRemoved(position);
+ }
+
+ public ArrayList<Uri> getAsArrayList() {
+ ArrayList<Uri> uris = new ArrayList<>();
+ for (ViewModel model : mDataset) {
+ uris.add(model.inputUri);
}
- return view;
+ return uris;
}
+
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java
index b242381b1..6f56f2dc4 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptAsymmetricFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2014-2015 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
@@ -44,7 +44,17 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
-public class EncryptAsymmetricFragment extends Fragment implements EncryptActivityInterface.UpdateListener {
+public class EncryptModeAsymmetricFragment extends Fragment {
+
+ public interface IAsymmetric {
+
+ public void onSignatureKeyIdChanged(long signatureKeyId);
+
+ public void onEncryptionKeyIdsChanged(long[] encryptionKeyIds);
+
+ public void onEncryptionUserIdsChanged(String[] encryptionUserIds);
+ }
+
ProviderHelper mProviderHelper;
// view
@@ -52,37 +62,43 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi
private EncryptKeyCompletionView mEncryptKeyView;
// model
- private EncryptActivityInterface mEncryptInterface;
+ private IAsymmetric mEncryptInterface;
- @Override
- public void onNotifyUpdate() {
- if (mSign != null) {
- mSign.setSelectedKeyId(mEncryptInterface.getSignatureKey());
- }
+// @Override
+// public void updateUi() {
+// if (mSign != null) {
+// mSign.setSelectedKeyId(mEncryptInterface.getSignatureKey());
+// }
+// }
+
+ public static final String ARG_SINGATURE_KEY_ID = "signature_key_id";
+ public static final String ARG_ENCRYPTION_KEY_IDS = "encryption_key_ids";
+
+
+ /**
+ * Creates new instance of this fragment
+ */
+ public static EncryptModeAsymmetricFragment newInstance(long signatureKey, long[] encryptionKeyIds) {
+ EncryptModeAsymmetricFragment frag = new EncryptModeAsymmetricFragment();
+
+ Bundle args = new Bundle();
+ args.putLong(ARG_SINGATURE_KEY_ID, signatureKey);
+ args.putLongArray(ARG_ENCRYPTION_KEY_IDS, encryptionKeyIds);
+ frag.setArguments(args);
+
+ return frag;
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
- mEncryptInterface = (EncryptActivityInterface) activity;
+ mEncryptInterface = (IAsymmetric) activity;
} catch (ClassCastException e) {
- throw new ClassCastException(activity + " must implement EncryptActivityInterface");
+ throw new ClassCastException(activity + " must implement IAsymmetric");
}
}
- private void setSignatureKeyId(long signatureKeyId) {
- mEncryptInterface.setSignatureKey(signatureKeyId);
- }
-
- private void setEncryptionKeyIds(long[] encryptionKeyIds) {
- mEncryptInterface.setEncryptionKeys(encryptionKeyIds);
- }
-
- private void setEncryptionUserIds(String[] encryptionUserIds) {
- mEncryptInterface.setEncryptionUsers(encryptionUserIds);
- }
-
/**
* Inflate the layout for this fragment
*/
@@ -94,7 +110,7 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi
mSign.setOnKeyChangedListener(new KeySpinner.OnKeyChangedListener() {
@Override
public void onKeyChanged(long masterKeyId) {
- setSignatureKeyId(masterKeyId);
+ mEncryptInterface.onSignatureKeyIdChanged(masterKeyId);
}
});
mEncryptKeyView = (EncryptKeyCompletionView) view.findViewById(R.id.recipient_list);
@@ -109,7 +125,9 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi
mProviderHelper = new ProviderHelper(getActivity());
// preselect keys given
- preselectKeys();
+ long signatureKeyId = getArguments().getLong(ARG_SINGATURE_KEY_ID);
+ long[] encryptionKeyIds = getArguments().getLongArray(ARG_ENCRYPTION_KEY_IDS);
+ preselectKeys(signatureKeyId, encryptionKeyIds);
mEncryptKeyView.setTokenListener(new TokenCompleteTextView.TokenListener() {
@Override
@@ -131,24 +149,20 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi
/**
* If an Intent gives a signatureMasterKeyId and/or encryptionMasterKeyIds, preselect those!
*/
- private void preselectKeys() {
- // TODO all of this works under the assumption that the first suitable subkey is always used!
- // not sure if we need to distinguish between different subkeys here?
- long signatureKey = mEncryptInterface.getSignatureKey();
- if (signatureKey != Constants.key.none) {
+ private void preselectKeys(long signatureKeyId, long[] encryptionKeyIds) {
+ if (signatureKeyId != Constants.key.none) {
try {
CachedPublicKeyRing keyring = mProviderHelper.getCachedPublicKeyRing(
- KeyRings.buildUnifiedKeyRingUri(signatureKey));
+ KeyRings.buildUnifiedKeyRingUri(signatureKeyId));
if (keyring.hasAnySecret()) {
- setSignatureKeyId(keyring.getMasterKeyId());
- mSign.setSelectedKeyId(mEncryptInterface.getSignatureKey());
+ mEncryptInterface.onSignatureKeyIdChanged(keyring.getMasterKeyId());
+ mSign.setSelectedKeyId(signatureKeyId);
}
} catch (PgpKeyNotFoundException e) {
Log.e(Constants.TAG, "key not found!", e);
}
}
- long[] encryptionKeyIds = mEncryptInterface.getEncryptionKeys();
if (encryptionKeyIds != null) {
for (long preselectedId : encryptionKeyIds) {
try {
@@ -181,7 +195,7 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi
for (int i = 0; i < keyIds.size(); i++) {
keyIdsArr[i] = iterator.next();
}
- setEncryptionKeyIds(keyIdsArr);
- setEncryptionUserIds(userIds.toArray(new String[userIds.size()]));
+ mEncryptInterface.onEncryptionKeyIdsChanged(keyIdsArr);
+ mEncryptInterface.onEncryptionUserIdsChanged(userIds.toArray(new String[userIds.size()]));
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptSymmetricFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeSymmetricFragment.java
index 36b3c08f9..48b1f4983 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptSymmetricFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeSymmetricFragment.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2014-2015 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
@@ -30,20 +30,37 @@ import android.widget.EditText;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.util.Passphrase;
-public class EncryptSymmetricFragment extends Fragment implements EncryptActivityInterface.UpdateListener {
+public class EncryptModeSymmetricFragment extends Fragment {
- EncryptActivityInterface mEncryptInterface;
+ public interface ISymmetric {
+
+ public void onPassphraseChanged(Passphrase passphrase);
+ }
+
+ private ISymmetric mEncryptInterface;
private EditText mPassphrase;
private EditText mPassphraseAgain;
+ /**
+ * Creates new instance of this fragment
+ */
+ public static EncryptModeSymmetricFragment newInstance() {
+ EncryptModeSymmetricFragment frag = new EncryptModeSymmetricFragment();
+
+ Bundle args = new Bundle();
+ frag.setArguments(args);
+
+ return frag;
+ }
+
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
- mEncryptInterface = (EncryptActivityInterface) activity;
+ mEncryptInterface = (ISymmetric) activity;
} catch (ClassCastException e) {
- throw new ClassCastException(activity.toString() + " must implement EncryptActivityInterface");
+ throw new ClassCastException(activity.toString() + " must implement ISymmetric");
}
}
@@ -74,9 +91,9 @@ public class EncryptSymmetricFragment extends Fragment implements EncryptActivit
p1.removeFromMemory();
p2.removeFromMemory();
if (passesEquals) {
- mEncryptInterface.setPassphrase(new Passphrase(mPassphrase.getText()));
+ mEncryptInterface.onPassphraseChanged(new Passphrase(mPassphrase.getText()));
} else {
- mEncryptInterface.setPassphrase(null);
+ mEncryptInterface.onPassphraseChanged(null);
}
}
};
@@ -86,8 +103,4 @@ public class EncryptSymmetricFragment extends Fragment implements EncryptActivit
return view;
}
- @Override
- public void onNotifyUpdate() {
-
- }
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java
index dd09e62c3..03ab48e23 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2012-2015 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
*
* This program is free software: you can redistribute it and/or modify
@@ -19,31 +19,21 @@
package org.sufficientlysecure.keychain.ui;
import android.content.Intent;
-import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
-import android.view.Menu;
-import android.view.MenuItem;
+import android.support.v4.app.FragmentTransaction;
+import android.view.View;
-import org.spongycastle.bcpg.CompressionAlgorithmTags;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.api.OpenKeychainIntents;
-import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
-import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
-import org.sufficientlysecure.keychain.pgp.KeyRing;
-import org.sufficientlysecure.keychain.pgp.PgpConstants;
-import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
-import org.sufficientlysecure.keychain.ui.util.Notify;
+import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase;
-import org.sufficientlysecure.keychain.util.ShareHelper;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Set;
-
-public class EncryptTextActivity extends EncryptActivity implements EncryptActivityInterface {
+public class EncryptTextActivity extends BaseActivity implements
+ EncryptModeAsymmetricFragment.IAsymmetric, EncryptModeSymmetricFragment.ISymmetric,
+ EncryptTextFragment.IMode {
/* Intents */
public static final String ACTION_ENCRYPT_TEXT = OpenKeychainIntents.ENCRYPT_TEXT;
@@ -55,285 +45,22 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv
public static final String EXTRA_SIGNATURE_KEY_ID = Constants.EXTRA_PREFIX + "EXTRA_SIGNATURE_KEY_ID";
public static final String EXTRA_ENCRYPTION_KEY_IDS = Constants.EXTRA_PREFIX + "EXTRA_SIGNATURE_KEY_IDS";
- // view
- private int mCurrentMode = MODE_ASYMMETRIC;
-
- // tabs
- private static final int MODE_ASYMMETRIC = 0;
- private static final int MODE_SYMMETRIC = 1;
-
- // model used by fragments
- private boolean mShareAfterEncrypt = false;
- private boolean mUseCompression = true;
- private boolean mHiddenRecipients = false;
-
- private long mEncryptionKeyIds[] = null;
- private String mEncryptionUserIds[] = null;
- // TODO Constants.key.none? What's wrong with a null value?
- private long mSigningKeyId = Constants.key.none;
- private Passphrase mPassphrase = new Passphrase();
-
- private ArrayList<Uri> mInputUris;
- private ArrayList<Uri> mOutputUris;
- private String mMessage = "";
-
- public boolean isModeSymmetric() {
- return MODE_SYMMETRIC == mCurrentMode;
- }
-
- @Override
- public boolean isUseArmor() {
- return true;
- }
-
- @Override
- public boolean isEncryptFilenames() {
- return false;
- }
-
- @Override
- public boolean isUseCompression() {
- return mUseCompression;
- }
-
- @Override
- public boolean isHiddenRecipients() {
- return mHiddenRecipients;
- }
-
- @Override
- public long getSignatureKey() {
- return mSigningKeyId;
- }
-
- @Override
- public long[] getEncryptionKeys() {
- return mEncryptionKeyIds;
- }
-
- @Override
- public String[] getEncryptionUsers() {
- return mEncryptionUserIds;
- }
-
- @Override
- public void setSignatureKey(long signatureKey) {
- mSigningKeyId = signatureKey;
- notifyUpdate();
- }
-
- @Override
- public void setEncryptionKeys(long[] encryptionKeys) {
- mEncryptionKeyIds = encryptionKeys;
- notifyUpdate();
- }
-
- @Override
- public void setEncryptionUsers(String[] encryptionUsers) {
- mEncryptionUserIds = encryptionUsers;
- notifyUpdate();
- }
-
- @Override
- public void setPassphrase(Passphrase passphrase) {
- if (mPassphrase != null) {
- mPassphrase.removeFromMemory();
- }
- mPassphrase = passphrase;
- }
-
- @Override
- public ArrayList<Uri> getInputUris() {
- if (mInputUris == null) mInputUris = new ArrayList<>();
- return mInputUris;
- }
-
- @Override
- public ArrayList<Uri> getOutputUris() {
- if (mOutputUris == null) mOutputUris = new ArrayList<>();
- return mOutputUris;
- }
-
- @Override
- public void setInputUris(ArrayList<Uri> uris) {
- mInputUris = uris;
- notifyUpdate();
- }
-
- @Override
- public void setOutputUris(ArrayList<Uri> uris) {
- mOutputUris = uris;
- notifyUpdate();
- }
-
- @Override
- public String getMessage() {
- return mMessage;
- }
-
- @Override
- public void setMessage(String message) {
- mMessage = message;
- }
-
- @Override
- public void notifyUpdate() {
- for (Fragment fragment : getSupportFragmentManager().getFragments()) {
- if (fragment instanceof UpdateListener) {
- ((UpdateListener) fragment).onNotifyUpdate();
- }
- }
- }
-
- @Override
- public void startEncrypt(boolean share) {
- mShareAfterEncrypt = share;
- startEncrypt();
- }
-
- @Override
- protected void onEncryptSuccess(SignEncryptResult result) {
- if (mShareAfterEncrypt) {
- // Share encrypted message/file
- startActivity(sendWithChooserExcludingEncrypt(result.getResultBytes()));
- } else {
- // Copy to clipboard
- copyToClipboard(result.getResultBytes());
- result.createNotify(EncryptTextActivity.this).show();
- // Notify.create(EncryptTextActivity.this,
- // R.string.encrypt_sign_clipboard_successful, Notify.Style.OK)
- // .show(getSupportFragmentManager().findFragmentById(R.id.encrypt_text_fragment));
- }
- }
-
- @Override
- protected SignEncryptParcel createEncryptBundle() {
- // fill values for this action
- SignEncryptParcel data = new SignEncryptParcel();
-
- data.setBytes(mMessage.getBytes());
- data.setCleartextSignature(true);
-
- if (mUseCompression) {
- data.setCompressionId(PgpConstants.sPreferredCompressionAlgorithms.get(0));
- } else {
- data.setCompressionId(CompressionAlgorithmTags.UNCOMPRESSED);
- }
- data.setHiddenRecipients(mHiddenRecipients);
- data.setSymmetricEncryptionAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED);
- data.setSignatureHashAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED);
-
- // Always use armor for messages
- data.setEnableAsciiArmorOutput(true);
-
- if (isModeSymmetric()) {
- Log.d(Constants.TAG, "Symmetric encryption enabled!");
- Passphrase passphrase = mPassphrase;
- if (passphrase.isEmpty()) {
- passphrase = null;
- }
- data.setSymmetricPassphrase(passphrase);
- } else {
- data.setEncryptionMasterKeyIds(mEncryptionKeyIds);
- data.setSignatureMasterKeyId(mSigningKeyId);
- data.setSignaturePassphrase(mSigningKeyPassphrase);
- data.setNfcState(mNfcHash, mNfcTimestamp);
- }
- return data;
- }
-
- private void copyToClipboard(byte[] resultBytes) {
- ClipboardReflection.copyToClipboard(this, new String(resultBytes));
- }
-
- /**
- * Create Intent Chooser but exclude OK's EncryptActivity.
- */
- private Intent sendWithChooserExcludingEncrypt(byte[] resultBytes) {
- Intent prototype = createSendIntent(resultBytes);
- String title = getString(R.string.title_share_message);
-
- // we don't want to encrypt the encrypted, no inception ;)
- String[] blacklist = new String[]{
- Constants.PACKAGE_NAME + ".ui.EncryptTextActivity",
- "org.thialfihar.android.apg.ui.EncryptActivity"
- };
-
- return new ShareHelper(this).createChooserExcluding(prototype, title, blacklist);
- }
-
- private Intent createSendIntent(byte[] resultBytes) {
- Intent sendIntent;
- sendIntent = new Intent(Intent.ACTION_SEND);
- sendIntent.setType(Constants.ENCRYPTED_TEXT_MIME);
- sendIntent.putExtra(Intent.EXTRA_TEXT, new String(resultBytes));
-
- if (!isModeSymmetric() && mEncryptionUserIds != null) {
- Set<String> users = new HashSet<>();
- for (String user : mEncryptionUserIds) {
- KeyRing.UserId userId = KeyRing.splitUserId(user);
- if (userId.email != null) {
- users.add(userId.email);
- }
- }
- // pass trough email addresses as extra for email applications
- sendIntent.putExtra(Intent.EXTRA_EMAIL, users.toArray(new String[users.size()]));
- }
- return sendIntent;
- }
-
- protected boolean inputIsValid() {
- if (mMessage == null) {
- Notify.create(this, R.string.error_message, Notify.Style.ERROR)
- .show(getSupportFragmentManager().findFragmentById(R.id.encrypt_text_fragment));
- return false;
- }
-
- if (isModeSymmetric()) {
- // symmetric encryption checks
-
- if (mPassphrase == null) {
- Notify.create(this, R.string.passphrases_do_not_match, Notify.Style.ERROR)
- .show(getSupportFragmentManager().findFragmentById(R.id.encrypt_text_fragment));
- return false;
- }
- if (mPassphrase.isEmpty()) {
- Notify.create(this, R.string.passphrase_must_not_be_empty, Notify.Style.ERROR)
- .show(getSupportFragmentManager().findFragmentById(R.id.encrypt_text_fragment));
- return false;
- }
-
- } else {
- // asymmetric encryption checks
-
- boolean gotEncryptionKeys = (mEncryptionKeyIds != null
- && mEncryptionKeyIds.length > 0);
-
- if (!gotEncryptionKeys && mSigningKeyId == 0) {
- Notify.create(this, R.string.select_encryption_or_signature_key, Notify.Style.ERROR)
- .show(getSupportFragmentManager().findFragmentById(R.id.encrypt_text_fragment));
- return false;
- }
- }
- return true;
- }
+ Fragment mModeFragment;
+ EncryptTextFragment mEncryptFragment;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- // if called with an intent action, do not init drawer navigation
- if (ACTION_ENCRYPT_TEXT.equals(getIntent().getAction())) {
- // lock drawer
-// deactivateDrawerNavigation();
- // TODO: back button to key?
- } else {
-// activateDrawerNavigation(savedInstanceState);
- }
+ setFullScreenDialogClose(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ finish();
+ }
+ }, false);
// Handle intent actions
- handleActions(getIntent());
- updateModeFragment();
+ handleActions(getIntent(), savedInstanceState);
}
@Override
@@ -341,58 +68,13 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv
setContentView(R.layout.encrypt_text_activity);
}
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- getMenuInflater().inflate(R.menu.encrypt_text_activity, menu);
- return super.onCreateOptionsMenu(menu);
- }
-
- private void updateModeFragment() {
- getSupportFragmentManager().beginTransaction()
- .replace(R.id.encrypt_pager_mode,
- mCurrentMode == MODE_SYMMETRIC
- ? new EncryptSymmetricFragment()
- : new EncryptAsymmetricFragment()
- )
- .commitAllowingStateLoss();
- getSupportFragmentManager().executePendingTransactions();
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- if (item.isCheckable()) {
- item.setChecked(!item.isChecked());
- }
- switch (item.getItemId()) {
- case R.id.check_use_symmetric: {
- mCurrentMode = item.isChecked() ? MODE_SYMMETRIC : MODE_ASYMMETRIC;
- updateModeFragment();
- notifyUpdate();
- break;
- }
- case R.id.check_enable_compression: {
- mUseCompression = item.isChecked();
- notifyUpdate();
- break;
- }
-// case R.id.check_hidden_recipients: {
-// mHiddenRecipients = item.isChecked();
-// notifyUpdate();
-// break;
-// }
- default: {
- return super.onOptionsItemSelected(item);
- }
- }
- return true;
- }
/**
* Handles all actions with this intent
*
* @param intent
*/
- private void handleActions(Intent intent) {
+ private void handleActions(Intent intent, Bundle savedInstanceState) {
String action = intent.getAction();
Bundle extras = intent.getExtras();
String type = intent.getType();
@@ -423,19 +105,61 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv
}
String textData = extras.getString(EXTRA_TEXT);
+ if (ACTION_ENCRYPT_TEXT.equals(action) && textData == null) {
+ Log.e(Constants.TAG, "Include the extra 'text' in your Intent!");
+ return;
+ }
// preselect keys given by intent
- mSigningKeyId = extras.getLong(EXTRA_SIGNATURE_KEY_ID);
- mEncryptionKeyIds = extras.getLongArray(EXTRA_ENCRYPTION_KEY_IDS);
+ long mSigningKeyId = extras.getLong(EXTRA_SIGNATURE_KEY_ID);
+ long[] mEncryptionKeyIds = extras.getLongArray(EXTRA_ENCRYPTION_KEY_IDS);
- /**
- * Main Actions
- */
- if (ACTION_ENCRYPT_TEXT.equals(action) && textData != null) {
- mMessage = textData;
- } else if (ACTION_ENCRYPT_TEXT.equals(action)) {
- Log.e(Constants.TAG, "Include the extra 'text' in your Intent!");
+
+ if (savedInstanceState == null) {
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+
+ mModeFragment = EncryptModeAsymmetricFragment.newInstance(mSigningKeyId, mEncryptionKeyIds);
+ transaction.replace(R.id.encrypt_mode_container, mModeFragment, "mode");
+
+ mEncryptFragment = EncryptTextFragment.newInstance(textData);
+ transaction.replace(R.id.encrypt_text_container, mEncryptFragment, "text");
+
+ transaction.commit();
+
+ getSupportFragmentManager().executePendingTransactions();
}
}
+ @Override
+ public void onModeChanged(boolean symmetric) {
+ // switch fragments
+ getSupportFragmentManager().beginTransaction()
+ .replace(R.id.encrypt_mode_container,
+ symmetric
+ ? EncryptModeSymmetricFragment.newInstance()
+ : EncryptModeAsymmetricFragment.newInstance(0, null)
+ )
+ .commitAllowingStateLoss();
+ getSupportFragmentManager().executePendingTransactions();
+ }
+
+ @Override
+ public void onSignatureKeyIdChanged(long signatureKeyId) {
+ mEncryptFragment.setSigningKeyId(signatureKeyId);
+ }
+
+ @Override
+ public void onEncryptionKeyIdsChanged(long[] encryptionKeyIds) {
+ mEncryptFragment.setEncryptionKeyIds(encryptionKeyIds);
+ }
+
+ @Override
+ public void onEncryptionUserIdsChanged(String[] encryptionUserIds) {
+ mEncryptFragment.setEncryptionUserIds(encryptionUserIds);
+ }
+
+ @Override
+ public void onPassphraseChanged(Passphrase passphrase) {
+ mEncryptFragment.setSymmetricPassphrase(passphrase);
+ }
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java
index 5d9994c5c..b37a2ca79 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2014-2015 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
@@ -18,32 +18,102 @@
package org.sufficientlysecure.keychain.ui;
import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.Intent;
import android.os.Bundle;
-import android.support.v4.app.Fragment;
+import android.os.Message;
+import android.os.Messenger;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
+import org.spongycastle.bcpg.CompressionAlgorithmTags;
+import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
+import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
+import org.sufficientlysecure.keychain.pgp.KeyRing;
+import org.sufficientlysecure.keychain.pgp.PgpConstants;
+import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
+import org.sufficientlysecure.keychain.service.KeychainIntentService;
+import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
+import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
+import org.sufficientlysecure.keychain.ui.util.Notify;
+import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.Passphrase;
+import org.sufficientlysecure.keychain.util.ShareHelper;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class EncryptTextFragment extends CryptoOperationFragment {
+
+ public interface IMode {
+ public void onModeChanged(boolean symmetric);
+ }
-public class EncryptTextFragment extends Fragment {
public static final String ARG_TEXT = "text";
+ private IMode mModeInterface;
+
+ private boolean mSymmetricMode = false;
+ private boolean mShareAfterEncrypt = false;
+ private boolean mUseCompression = true;
+ private boolean mHiddenRecipients = false;
+
+ private long mEncryptionKeyIds[] = null;
+ private String mEncryptionUserIds[] = null;
+ // TODO Constants.key.none? What's wrong with a null value?
+ private long mSigningKeyId = Constants.key.none;
+ private Passphrase mSymmetricPassphrase = new Passphrase();
+ private String mMessage = "";
+
private TextView mText;
- private EncryptActivityInterface mEncryptInterface;
+ public void setEncryptionKeyIds(long[] encryptionKeyIds) {
+ mEncryptionKeyIds = encryptionKeyIds;
+ }
+
+ public void setEncryptionUserIds(String[] encryptionUserIds) {
+ mEncryptionUserIds = encryptionUserIds;
+ }
+
+ public void setSigningKeyId(long signingKeyId) {
+ mSigningKeyId = signingKeyId;
+ }
+
+ public void setSymmetricPassphrase(Passphrase passphrase) {
+ mSymmetricPassphrase = passphrase;
+ }
+
+ /**
+ * Creates new instance of this fragment
+ */
+ public static EncryptTextFragment newInstance(String text) {
+ EncryptTextFragment frag = new EncryptTextFragment();
+
+ Bundle args = new Bundle();
+ args.putString(ARG_TEXT, text);
+ frag.setArguments(args);
+
+ return frag;
+ }
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
- mEncryptInterface = (EncryptActivityInterface) activity;
+ mModeInterface = (IMode) activity;
} catch (ClassCastException e) {
- throw new ClassCastException(activity.toString() + " must implement EncryptActivityInterface");
+ throw new ClassCastException(activity.toString() + " must implement IMode");
}
}
@@ -68,39 +138,58 @@ public class EncryptTextFragment extends Fragment {
@Override
public void afterTextChanged(Editable s) {
- mEncryptInterface.setMessage(s.toString());
+ mMessage = s.toString();
}
});
+ // set initial text
+ if (mMessage != null) {
+ mText.setText(mMessage);
+ }
+
return view;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ mMessage = getArguments().getString(ARG_TEXT);
setHasOptionsMenu(true);
}
@Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
-
- String text = mEncryptInterface.getMessage();
- if (text != null) {
- mText.setText(text);
- }
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ super.onCreateOptionsMenu(menu, inflater);
+ inflater.inflate(R.menu.encrypt_text_fragment, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.isCheckable()) {
+ item.setChecked(!item.isChecked());
+ }
switch (item.getItemId()) {
+ case R.id.check_use_symmetric: {
+ mSymmetricMode = item.isChecked();
+ mModeInterface.onModeChanged(mSymmetricMode);
+ break;
+ }
+ case R.id.check_enable_compression: {
+ mUseCompression = item.isChecked();
+ break;
+ }
+// case R.id.check_hidden_recipients: {
+// mHiddenRecipients = item.isChecked();
+// notifyUpdate();
+// break;
+// }
case R.id.encrypt_copy: {
- mEncryptInterface.startEncrypt(false);
+ startEncrypt(false);
break;
}
case R.id.encrypt_share: {
- mEncryptInterface.startEncrypt(true);
+ startEncrypt(true);
break;
}
default: {
@@ -109,4 +198,189 @@ public class EncryptTextFragment extends Fragment {
}
return true;
}
+
+
+ protected void onEncryptSuccess(SignEncryptResult result) {
+ if (mShareAfterEncrypt) {
+ // Share encrypted message/file
+ startActivity(sendWithChooserExcludingEncrypt(result.getResultBytes()));
+ } else {
+ // Copy to clipboard
+ copyToClipboard(result.getResultBytes());
+ result.createNotify(getActivity()).show();
+ // Notify.create(EncryptTextActivity.this,
+ // R.string.encrypt_sign_clipboard_successful, Notify.Style.OK)
+ // .show(getSupportFragmentManager().findFragmentById(R.id.encrypt_text_fragment));
+ }
+ }
+
+ protected SignEncryptParcel createEncryptBundle() {
+ // fill values for this action
+ SignEncryptParcel data = new SignEncryptParcel();
+
+ data.setBytes(mMessage.getBytes());
+ data.setCleartextSignature(true);
+
+ if (mUseCompression) {
+ data.setCompressionId(PgpConstants.sPreferredCompressionAlgorithms.get(0));
+ } else {
+ data.setCompressionId(CompressionAlgorithmTags.UNCOMPRESSED);
+ }
+ data.setHiddenRecipients(mHiddenRecipients);
+ data.setSymmetricEncryptionAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED);
+ data.setSignatureHashAlgorithm(PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED);
+
+ // Always use armor for messages
+ data.setEnableAsciiArmorOutput(true);
+
+ if (mSymmetricMode) {
+ Log.d(Constants.TAG, "Symmetric encryption enabled!");
+ Passphrase passphrase = mSymmetricPassphrase;
+ if (passphrase.isEmpty()) {
+ passphrase = null;
+ }
+ data.setSymmetricPassphrase(passphrase);
+ } else {
+ data.setEncryptionMasterKeyIds(mEncryptionKeyIds);
+ data.setSignatureMasterKeyId(mSigningKeyId);
+ }
+ return data;
+ }
+
+ private void copyToClipboard(byte[] resultBytes) {
+ ClipboardReflection.copyToClipboard(getActivity(), new String(resultBytes));
+ }
+
+ /**
+ * Create Intent Chooser but exclude OK's EncryptActivity.
+ */
+ private Intent sendWithChooserExcludingEncrypt(byte[] resultBytes) {
+ Intent prototype = createSendIntent(resultBytes);
+ String title = getString(R.string.title_share_message);
+
+ // we don't want to encrypt the encrypted, no inception ;)
+ String[] blacklist = new String[]{
+ Constants.PACKAGE_NAME + ".ui.EncryptTextActivity",
+ "org.thialfihar.android.apg.ui.EncryptActivity"
+ };
+
+ return new ShareHelper(getActivity()).createChooserExcluding(prototype, title, blacklist);
+ }
+
+ private Intent createSendIntent(byte[] resultBytes) {
+ Intent sendIntent;
+ sendIntent = new Intent(Intent.ACTION_SEND);
+ sendIntent.setType(Constants.ENCRYPTED_TEXT_MIME);
+ sendIntent.putExtra(Intent.EXTRA_TEXT, new String(resultBytes));
+
+ if (!mSymmetricMode && mEncryptionUserIds != null) {
+ Set<String> users = new HashSet<>();
+ for (String user : mEncryptionUserIds) {
+ KeyRing.UserId userId = KeyRing.splitUserId(user);
+ if (userId.email != null) {
+ users.add(userId.email);
+ }
+ }
+ // pass trough email addresses as extra for email applications
+ sendIntent.putExtra(Intent.EXTRA_EMAIL, users.toArray(new String[users.size()]));
+ }
+ return sendIntent;
+ }
+
+ protected boolean inputIsValid() {
+ if (mMessage == null) {
+ Notify.create(getActivity(), R.string.error_message, Notify.Style.ERROR)
+ .show();
+ return false;
+ }
+
+ if (mSymmetricMode) {
+ // symmetric encryption checks
+
+ if (mSymmetricPassphrase == null) {
+ Notify.create(getActivity(), R.string.passphrases_do_not_match, Notify.Style.ERROR)
+ .show();
+ return false;
+ }
+ if (mSymmetricPassphrase.isEmpty()) {
+ Notify.create(getActivity(), R.string.passphrase_must_not_be_empty, Notify.Style.ERROR)
+ .show();
+ return false;
+ }
+
+ } else {
+ // asymmetric encryption checks
+
+ boolean gotEncryptionKeys = (mEncryptionKeyIds != null
+ && mEncryptionKeyIds.length > 0);
+
+ if (!gotEncryptionKeys && mSigningKeyId == 0) {
+ Notify.create(getActivity(), R.string.select_encryption_or_signature_key, Notify.Style.ERROR)
+ .show();
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+ public void startEncrypt(boolean share) {
+ mShareAfterEncrypt = share;
+ cryptoOperation();
+ }
+
+ @Override
+ protected void cryptoOperation(CryptoInputParcel cryptoInput) {
+ if (!inputIsValid()) {
+ // Notify was created by inputIsValid.
+ return;
+ }
+
+ // Send all information needed to service to edit key in other thread
+ Intent intent = new Intent(getActivity(), KeychainIntentService.class);
+ intent.setAction(KeychainIntentService.ACTION_SIGN_ENCRYPT);
+
+ final SignEncryptParcel input = createEncryptBundle();
+ final Bundle data = new Bundle();
+ data.putParcelable(KeychainIntentService.SIGN_ENCRYPT_PARCEL, input);
+ data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
+ intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
+
+ // Message is received after encrypting is done in KeychainIntentService
+ ServiceProgressHandler serviceHandler = new ServiceProgressHandler(
+ getActivity(),
+ getString(R.string.progress_encrypting),
+ ProgressDialog.STYLE_HORIZONTAL,
+ ProgressDialogFragment.ServiceType.KEYCHAIN_INTENT) {
+ public void handleMessage(Message message) {
+ // handle messages by standard KeychainIntentServiceHandler first
+ super.handleMessage(message);
+
+ if (handlePendingMessage(message)) {
+ return;
+ }
+
+ if (message.arg1 == MessageStatus.OKAY.ordinal()) {
+ SignEncryptResult result =
+ message.getData().getParcelable(SignEncryptResult.EXTRA_RESULT);
+
+ if (result.success()) {
+ onEncryptSuccess(result);
+ } else {
+ result.createNotify(getActivity()).show();
+ }
+ }
+ }
+ };
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(serviceHandler);
+ intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
+
+ // show progress dialog
+ serviceHandler.showProgressDialog(getActivity());
+
+ // start service with intent
+ getActivity().startService(intent);
+ }
+
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java
index 6c3336547..c757c8e88 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpActivity.java
@@ -26,6 +26,8 @@ import com.astuetz.PagerSlidingTabStrip;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
+import org.sufficientlysecure.keychain.ui.base.BaseActivity;
+
public class HelpActivity extends BaseActivity {
public static final String EXTRA_SELECTED_TAB = "selected_tab";
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java
index dc4a2eb10..7fe5be793 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java
@@ -35,6 +35,7 @@ import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
+import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
import org.sufficientlysecure.keychain.service.CloudImportService;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
@@ -47,7 +48,8 @@ import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize
import java.io.IOException;
import java.util.ArrayList;
-public class ImportKeysActivity extends BaseActivity {
+public class ImportKeysActivity extends BaseNfcActivity {
+
public static final String ACTION_IMPORT_KEY = OpenKeychainIntents.IMPORT_KEY;
public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER = OpenKeychainIntents.IMPORT_KEY_FROM_KEYSERVER;
public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT =
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java
index 6a6140892..b9fdbea5c 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java
@@ -53,9 +53,11 @@ import java.util.List;
public class ImportKeysListFragment extends ListFragment implements
LoaderManager.LoaderCallbacks<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> {
+
private static final String ARG_DATA_URI = "uri";
private static final String ARG_BYTES = "bytes";
- private static final String ARG_SERVER_QUERY = "query";
+ public static final String ARG_SERVER_QUERY = "query";
+ public static final String ARG_NON_INTERACTIVE = "non_interactive";
private Activity mActivity;
private ImportKeysAdapter mAdapter;
@@ -66,6 +68,7 @@ public class ImportKeysListFragment extends ListFragment implements
private static final int LOADER_ID_CLOUD = 1;
private LongSparseArray<ParcelableKeyRing> mCachedKeyData;
+ private boolean mNonInteractive;
public LoaderState getLoaderState() {
return mLoaderState;
@@ -118,16 +121,19 @@ public class ImportKeysListFragment extends ListFragment implements
}
- /**
- * Creates new instance of this fragment
- */
public static ImportKeysListFragment newInstance(byte[] bytes, Uri dataUri, String serverQuery) {
+ return newInstance(bytes, dataUri, serverQuery, false);
+ }
+
+ public static ImportKeysListFragment newInstance(byte[] bytes, Uri dataUri,
+ String serverQuery, boolean nonInteractive) {
ImportKeysListFragment frag = new ImportKeysListFragment();
Bundle args = new Bundle();
args.putByteArray(ARG_BYTES, bytes);
args.putParcelable(ARG_DATA_URI, dataUri);
args.putString(ARG_SERVER_QUERY, serverQuery);
+ args.putBoolean(ARG_NON_INTERACTIVE, nonInteractive);
frag.setArguments(args);
@@ -173,9 +179,11 @@ public class ImportKeysListFragment extends ListFragment implements
mAdapter = new ImportKeysAdapter(mActivity);
setListAdapter(mAdapter);
- Uri dataUri = getArguments().getParcelable(ARG_DATA_URI);
- byte[] bytes = getArguments().getByteArray(ARG_BYTES);
- String query = getArguments().getString(ARG_SERVER_QUERY);
+ Bundle args = getArguments();
+ Uri dataUri = args.containsKey(ARG_DATA_URI) ? args.<Uri>getParcelable(ARG_DATA_URI) : null;
+ byte[] bytes = args.containsKey(ARG_BYTES) ? args.getByteArray(ARG_BYTES) : null;
+ String query = args.containsKey(ARG_SERVER_QUERY) ? args.getString(ARG_SERVER_QUERY) : null;
+ mNonInteractive = args.containsKey(ARG_NON_INTERACTIVE) ? args.getBoolean(ARG_NON_INTERACTIVE) : false;
if (dataUri != null || bytes != null) {
mLoaderState = new BytesLoaderState(bytes, dataUri);
@@ -203,6 +211,10 @@ public class ImportKeysListFragment extends ListFragment implements
public void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
+ if (mNonInteractive) {
+ return;
+ }
+
// Select checkbox!
// Update underlying data and notify adapter of change. The adapter will
// update the view automatically.
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java
index 21747f77b..b9f1bf870 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java
@@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.ui;
import android.annotation.TargetApi;
import android.app.ProgressDialog;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.net.Uri;
import android.nfc.NdefMessage;
import android.nfc.NfcAdapter;
@@ -83,25 +84,22 @@ public class ImportKeysProxyActivity extends FragmentActivity {
returnResult = false;
processScannedContent(dataUri);
- } else if (ACTION_SCAN_IMPORT.equals(action)) {
+ } else if (ACTION_SCAN_IMPORT.equals(action) || ACTION_QR_CODE_API.equals(action)) {
returnResult = false;
IntentIntegrator integrator = new IntentIntegrator(this);
integrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE_TYPES)
.setPrompt(getString(R.string.import_qr_code_text))
- .setResultDisplayDuration(0)
- .initiateScan();
+ .setResultDisplayDuration(0);
+ integrator.setOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+ integrator.initiateScan();
} else if (ACTION_SCAN_WITH_RESULT.equals(action)) {
returnResult = true;
IntentIntegrator integrator = new IntentIntegrator(this);
integrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE_TYPES)
.setPrompt(getString(R.string.import_qr_code_text))
- .setResultDisplayDuration(0)
- .initiateScan();
- } else if (ACTION_QR_CODE_API.equals(action)) {
- // scan using xzing's Barcode Scanner from outside OpenKeychain
-
- returnResult = false;
- new IntentIntegrator(this).initiateScan();
+ .setResultDisplayDuration(0);
+ integrator.setOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+ integrator.initiateScan();
} else if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
// Check to see if the Activity started due to an Android Beam
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayActivity.java
index 0de7bb391..df325d31d 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayActivity.java
@@ -22,6 +22,8 @@ import android.os.Bundle;
import android.view.View;
import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.ui.base.BaseActivity;
+
public class LogDisplayActivity extends BaseActivity {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcIntentActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcIntentActivity.java
deleted file mode 100644
index 0ccb206d1..000000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcIntentActivity.java
+++ /dev/null
@@ -1,313 +0,0 @@
-/**
- * Copyright (c) 2013-2014 Philipp Jakubeit, Signe Rüsch, Dominik Schürmann
- *
- * Licensed under the Bouncy Castle License (MIT license). See LICENSE file for details.
- */
-
-package org.sufficientlysecure.keychain.ui;
-
-import android.annotation.TargetApi;
-import android.app.PendingIntent;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.nfc.NfcAdapter;
-import android.nfc.Tag;
-import android.nfc.tech.IsoDep;
-import android.os.Build;
-import android.os.Bundle;
-import android.view.WindowManager;
-import android.widget.Toast;
-
-import org.spongycastle.util.encoders.Hex;
-import org.sufficientlysecure.keychain.Constants;
-import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
-import org.sufficientlysecure.keychain.util.Iso7816TLV;
-import org.sufficientlysecure.keychain.util.Log;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-
-/**
- * This class provides a communication interface to OpenPGP applications on ISO SmartCard compliant
- * NFC devices.
- *
- * For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf
- */
-@TargetApi(Build.VERSION_CODES.GINGERBREAD_MR1)
-public class NfcIntentActivity extends BaseActivity {
-
- // special extra for OpenPgpService
- public static final String EXTRA_DATA = "data";
-
- private Intent mServiceIntent;
-
- private static final int TIMEOUT = 100000;
-
- private NfcAdapter mNfcAdapter;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- Log.d(Constants.TAG, "NfcActivity.onCreate");
-
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-
- Intent intent = getIntent();
- Bundle data = intent.getExtras();
- String action = intent.getAction();
-
- Log.d(Constants.TAG, action);
- Log.d(Constants.TAG, intent.getDataString());
-
- // TODO check fingerprint
- // mFingerprint = data.getByteArray(EXTRA_FINGERPRINT);
-
- if (!NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
- Log.d(Constants.TAG, "Action not supported: " + action);
- finish();
- }
-
- try {
- Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
-
- // Connect to the detected tag, setting a couple of settings
- IsoDep isoDep = IsoDep.get(detectedTag);
- isoDep.setTimeout(TIMEOUT); // timeout is set to 100 seconds to avoid cancellation during calculation
- isoDep.connect();
-
- nfcGreet(isoDep);
- // nfcPin(isoDep, "yoloswag");
- nfcGetFingerprint(isoDep);
-
- } catch (IOException e) {
- Log.e(Constants.TAG, "IOException!", e);
- finish();
- }
- }
-
- @Override
- protected void initLayout() {
- setContentView(R.layout.nfc_activity);
- }
-
- /**
- * Called when the system is about to start resuming a previous activity,
- * disables NFC Foreground Dispatch
- */
- public void onPause() {
- super.onPause();
- Log.d(Constants.TAG, "NfcActivity.onPause");
-
- // disableNfcForegroundDispatch();
- }
-
- /**
- * Called when the activity will start interacting with the user,
- * enables NFC Foreground Dispatch
- */
- public void onResume() {
- super.onResume();
- Log.d(Constants.TAG, "NfcActivity.onResume");
-
- // enableNfcForegroundDispatch();
- }
-
- /** Handle NFC communication and return a result.
- *
- * This method is called by onNewIntent above upon discovery of an NFC tag.
- * It handles initialization and login to the application, subsequently
- * calls either nfcCalculateSignature() or nfcDecryptSessionKey(), then
- * finishes the activity with an appropiate result.
- *
- * On general communication, see also
- * http://www.cardwerk.com/smartcards/smartcard_standard_ISO7816-4_annex-a.aspx
- *
- * References to pages are generally related to the OpenPGP Application
- * on ISO SmartCard Systems specification.
- *
- */
- private void nfcGreet(IsoDep isoDep) throws IOException {
-
- // SW1/2 0x9000 is the generic "ok" response, which we expect most of the time.
- // See specification, page 51
- String accepted = "9000";
-
- // Command APDU (page 51) for SELECT FILE command (page 29)
- String opening =
- "00" // CLA
- + "A4" // INS
- + "04" // P1
- + "00" // P2
- + "06" // Lc (number of bytes)
- + "D27600012401" // Data (6 bytes)
- + "00"; // Le
- if (!card(isoDep, opening).equals(accepted)) { // activate connection
- toast("Opening Error!");
- setResult(RESULT_CANCELED, mServiceIntent);
- finish();
- }
- }
-
- private void nfcPin(IsoDep isoDep, String pin) throws IOException {
-
- String data = "00CA006E00";
- String fingerprint = card(isoDep, data);
-
- // Command APDU for VERIFY command (page 32)
- String login =
- "00" // CLA
- + "20" // INS
- + "00" // P1
- + "82" // P2 (PW1)
- + String.format("%02x", pin.length()) // Lc
- + Hex.toHexString(pin.getBytes());
- if ( ! card(isoDep, login).equals("9000")) { // login
- toast("Pin Error!");
- setResult(RESULT_CANCELED, mServiceIntent);
- finish();
- }
-
- }
-
- /**
- * Gets the user ID
- *
- * @return the user id as "name <email>"
- * @throws java.io.IOException
- */
- public static String getUserId(IsoDep isoDep) throws IOException {
- String info = "00CA006500";
- String data = "00CA005E00";
- return getName(card(isoDep, info)) + " <" + (new String(Hex.decode(getDataField(card(isoDep, data))))) + ">";
- }
-
- /**
- * Gets the long key ID
- *
- * @return the long key id (last 16 bytes of the fingerprint)
- * @throws java.io.IOException
- */
- public static long getKeyId(IsoDep isoDep) throws IOException {
- String keyId = nfcGetFingerprint(isoDep).substring(24);
- Log.d(Constants.TAG, "keyId: " + keyId);
- return Long.parseLong(keyId, 16);
- }
-
- /**
- * Gets the fingerprint of the signature key
- *
- * @return the fingerprint
- * @throws java.io.IOException
- */
- public static String nfcGetFingerprint(IsoDep isoDep) throws IOException {
- String data = "00CA006E00";
- byte[] buf = isoDep.transceive(Hex.decode(data));
-
- Iso7816TLV tlv = Iso7816TLV.readSingle(buf, true);
- Log.d(Constants.TAG, "nfc tlv data:\n" + tlv.prettyPrint());
-
- Iso7816TLV fptlv = Iso7816TLV.findRecursive(tlv, 0xc5);
- if (fptlv != null) {
- ByteBuffer fpbuf = ByteBuffer.wrap(fptlv.mV);
- byte[] fp = new byte[20];
- fpbuf.get(fp);
- Log.d(Constants.TAG, "fingerprint 1: " + KeyFormattingUtils.convertFingerprintToHex(fp));
- fpbuf.get(fp);
- Log.d(Constants.TAG, "fingerprint 2: " + KeyFormattingUtils.convertFingerprintToHex(fp));
- fpbuf.get(fp);
- Log.d(Constants.TAG, "fingerprint 3: " + KeyFormattingUtils.convertFingerprintToHex(fp));
- }
-
- return "nope";
- }
-
- /**
- * Prints a message to the screen
- *
- * @param text the text which should be contained within the toast
- */
- private void toast(String text) {
- Toast.makeText(this, text, Toast.LENGTH_LONG).show();
- }
-
- /**
- * Receive new NFC Intents to this activity only by enabling foreground dispatch.
- * This can only be done in onResume!
- */
- public void enableNfcForegroundDispatch() {
- mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
- Intent nfcI = new Intent(this, NfcIntentActivity.class)
- .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- PendingIntent nfcPendingIntent = PendingIntent.getActivity(this, 0, nfcI, 0);
- IntentFilter[] writeTagFilters = new IntentFilter[]{
- new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED)
- };
-
- // https://code.google.com/p/android/issues/detail?id=62918
- // maybe mNfcAdapter.enableReaderMode(); ?
- try {
- mNfcAdapter.enableForegroundDispatch(this, nfcPendingIntent, writeTagFilters, null);
- } catch (IllegalStateException e) {
- Log.i(Constants.TAG, "NfcForegroundDispatch Error!", e);
- }
- Log.d(Constants.TAG, "NfcForegroundDispatch has been enabled!");
- }
-
- /**
- * Disable foreground dispatch in onPause!
- */
- public void disableNfcForegroundDispatch() {
- mNfcAdapter.disableForegroundDispatch(this);
- Log.d(Constants.TAG, "NfcForegroundDispatch has been disabled!");
- }
-
- /**
- * Gets the name of the user out of the raw card output regarding card holder related data
- *
- * @param name the raw card holder related data from the card
- * @return the name given in this data
- */
- public static String getName(String name) {
- String slength;
- int ilength;
- name = name.substring(6);
- slength = name.substring(0, 2);
- ilength = Integer.parseInt(slength, 16) * 2;
- name = name.substring(2, ilength + 2);
- name = (new String(Hex.decode(name))).replace('<', ' ');
- return (name);
- }
-
- /**
- * Reduces the raw data from the card by four characters
- *
- * @param output the raw data from the card
- * @return the data field of that data
- */
- private static String getDataField(String output) {
- return output.substring(0, output.length() - 4);
- }
-
- /**
- * Communicates with the OpenPgpCard via the APDU
- *
- * @param hex the hexadecimal APDU
- * @return The answer from the card
- * @throws java.io.IOException throws an exception if something goes wrong
- */
- public static String card(IsoDep isoDep, String hex) throws IOException {
- return getHex(isoDep.transceive(Hex.decode(hex)));
- }
-
- /**
- * Converts a byte array into an hex string
- *
- * @param raw the byte array representation
- * @return the hexadecimal string representation
- */
- public static String getHex(byte[] raw) {
- return new String(Hex.encode(raw));
- }
-
-}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java
new file mode 100644
index 000000000..aa66053fa
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java
@@ -0,0 +1,121 @@
+/**
+ * Copyright (c) 2013-2014 Philipp Jakubeit, Signe Rüsch, Dominik Schürmann
+ *
+ * Licensed under the Bouncy Castle License (MIT license). See LICENSE file for details.
+ */
+
+package org.sufficientlysecure.keychain.ui;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.remote.CryptoInputParcelCacheService;
+import org.sufficientlysecure.keychain.service.PassphraseCacheService;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
+import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
+import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.Preferences;
+
+import java.io.IOException;
+
+/**
+ * This class provides a communication interface to OpenPGP applications on ISO SmartCard compliant
+ * NFC devices.
+ * <p/>
+ * For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf
+ */
+public class NfcOperationActivity extends BaseNfcActivity {
+
+ public static final String EXTRA_REQUIRED_INPUT = "required_input";
+
+ // passthrough for OpenPgpService
+ public static final String EXTRA_SERVICE_INTENT = "data";
+
+ public static final String RESULT_DATA = "result_data";
+
+ private RequiredInputParcel mRequiredInput;
+ private Intent mServiceIntent;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Log.d(Constants.TAG, "NfcOperationActivity.onCreate");
+
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+
+ Intent intent = getIntent();
+ Bundle data = intent.getExtras();
+
+ mRequiredInput = data.getParcelable(EXTRA_REQUIRED_INPUT);
+ mServiceIntent = data.getParcelable(EXTRA_SERVICE_INTENT);
+
+ // obtain passphrase for this subkey
+ obtainYubiKeyPin(RequiredInputParcel.createRequiredPassphrase(mRequiredInput));
+ }
+
+ @Override
+ protected void initLayout() {
+ setContentView(R.layout.nfc_activity);
+ }
+
+ @Override
+ protected void onNfcPerform() throws IOException {
+
+ CryptoInputParcel inputParcel = new CryptoInputParcel(mRequiredInput.mSignatureTime);
+
+ switch (mRequiredInput.mType) {
+ case NFC_DECRYPT: {
+ for (int i = 0; i < mRequiredInput.mInputHashes.length; i++) {
+ byte[] hash = mRequiredInput.mInputHashes[i];
+ byte[] decryptedSessionKey = nfcDecryptSessionKey(hash);
+ inputParcel.addCryptoData(hash, decryptedSessionKey);
+ }
+ break;
+ }
+ case NFC_SIGN: {
+ for (int i = 0; i < mRequiredInput.mInputHashes.length; i++) {
+ byte[] hash = mRequiredInput.mInputHashes[i];
+ int algo = mRequiredInput.mSignAlgos[i];
+ byte[] signedHash = nfcCalculateSignature(hash, algo);
+ inputParcel.addCryptoData(hash, signedHash);
+ }
+ break;
+ }
+ }
+
+ if (mServiceIntent != null) {
+ CryptoInputParcelCacheService.addCryptoInputParcel(this, mServiceIntent, inputParcel);
+ setResult(RESULT_OK, mServiceIntent);
+ } else {
+ Intent result = new Intent();
+ result.putExtra(NfcOperationActivity.RESULT_DATA, inputParcel);
+ setResult(RESULT_OK, result);
+ }
+
+ finish();
+ }
+
+ @Override
+ public void handlePinError() {
+
+ // avoid a loop
+ Preferences prefs = Preferences.getPreferences(this);
+ if (prefs.useDefaultYubiKeyPin()) {
+ toast(getString(R.string.error_pin_nodefault));
+ setResult(RESULT_CANCELED);
+ finish();
+ return;
+ }
+
+ // clear (invalid) passphrase
+ PassphraseCacheService.clearCachedPassphrase(
+ this, mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId());
+
+ obtainYubiKeyPin(RequiredInputParcel.createRequiredPassphrase(mRequiredInput));
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java
index b77637696..c6431bfaf 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java
@@ -52,7 +52,10 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.remote.CryptoInputParcelCacheService;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase;
@@ -63,13 +66,14 @@ import org.sufficientlysecure.keychain.util.Preferences;
* This activity encapsulates a DialogFragment to emulate a dialog.
*/
public class PassphraseDialogActivity extends FragmentActivity {
- public static final String MESSAGE_DATA_PASSPHRASE = "passphrase";
- public static final String EXTRA_KEY_ID = "key_id";
+ public static final String RESULT_CRYPTO_INPUT = "result_data";
+
+ public static final String EXTRA_REQUIRED_INPUT = "required_input";
public static final String EXTRA_SUBKEY_ID = "secret_key_id";
// special extra for OpenPgpService
- public static final String EXTRA_DATA = "data";
+ public static final String EXTRA_SERVICE_INTENT = "data";
private static final int REQUEST_CODE_ENTER_PATTERN = 2;
@@ -88,9 +92,27 @@ public class PassphraseDialogActivity extends FragmentActivity {
// this activity itself has no content view (see manifest)
- long keyId = getIntent().getLongExtra(EXTRA_SUBKEY_ID, 0);
+ long keyId;
+ if (getIntent().hasExtra(EXTRA_SUBKEY_ID)) {
+ keyId = getIntent().getLongExtra(EXTRA_SUBKEY_ID, 0);
+ } else {
+ RequiredInputParcel requiredInput = getIntent().getParcelableExtra(EXTRA_REQUIRED_INPUT);
+ switch (requiredInput.mType) {
+ case PASSPHRASE_SYMMETRIC: {
+ keyId = Constants.key.symmetric;
+ break;
+ }
+ case PASSPHRASE: {
+ keyId = requiredInput.getSubKeyId();
+ break;
+ }
+ default: {
+ throw new AssertionError("Unsupported required input type for PassphraseDialogActivity!");
+ }
+ }
+ }
- Intent serviceIntent = getIntent().getParcelableExtra(EXTRA_DATA);
+ Intent serviceIntent = getIntent().getParcelableExtra(EXTRA_SERVICE_INTENT);
show(this, keyId, serviceIntent);
}
@@ -141,7 +163,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
PassphraseDialogFragment frag = new PassphraseDialogFragment();
Bundle args = new Bundle();
args.putLong(EXTRA_SUBKEY_ID, keyId);
- args.putParcelable(EXTRA_DATA, serviceIntent);
+ args.putParcelable(EXTRA_SERVICE_INTENT, serviceIntent);
frag.setArguments(args);
@@ -174,11 +196,12 @@ public class PassphraseDialogActivity extends FragmentActivity {
R.style.Theme_AppCompat_Light_Dialog);
mSubKeyId = getArguments().getLong(EXTRA_SUBKEY_ID);
- mServiceIntent = getArguments().getParcelable(EXTRA_DATA);
+ mServiceIntent = getArguments().getParcelable(EXTRA_SERVICE_INTENT);
CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(theme);
- alert.setTitle(R.string.title_unlock);
+ // No title, see http://www.google.com/design/spec/components/dialogs.html#dialogs-alerts
+ //alert.setTitle()
LayoutInflater inflater = LayoutInflater.from(theme);
View view = inflater.inflate(R.layout.passphrase_dialog, null);
@@ -243,8 +266,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
case PASSPHRASE_EMPTY:
finishCaching(new Passphrase(""));
default:
- message = "This should not happen!";
- break;
+ throw new AssertionError("Unhandled SecretKeyType (should not happen)");
}
} catch (ProviderHelper.NotFoundException e) {
@@ -296,7 +318,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
mPassphraseEditText.setImeActionLabel(getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE);
mPassphraseEditText.setOnEditorActionListener(this);
- if (keyType == CanonicalizedSecretKey.SecretKeyType.DIVERT_TO_CARD && Preferences.getPreferences(activity).useNumKeypadForYubikeyPin()) {
+ if (keyType == CanonicalizedSecretKey.SecretKeyType.DIVERT_TO_CARD && Preferences.getPreferences(activity).useNumKeypadForYubiKeyPin()) {
mPassphraseEditText.setRawInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_TEXT_VARIATION_PASSWORD);
} else if (keyType == CanonicalizedSecretKey.SecretKeyType.PIN) {
mPassphraseEditText.setRawInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_TEXT_VARIATION_PASSWORD);
@@ -309,7 +331,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
AlertDialog dialog = alert.create();
dialog.setButton(DialogInterface.BUTTON_POSITIVE,
- activity.getString(android.R.string.ok), (DialogInterface.OnClickListener) null);
+ activity.getString(R.string.btn_unlock), (DialogInterface.OnClickListener) null);
return dialog;
}
@@ -406,17 +428,14 @@ public class PassphraseDialogActivity extends FragmentActivity {
return;
}
+ CryptoInputParcel inputParcel = new CryptoInputParcel(null, passphrase);
if (mServiceIntent != null) {
- // TODO: Not routing passphrase through OpenPGP API currently
- // due to security concerns...
- // BUT this means you need to _cache_ passphrases!
+ CryptoInputParcelCacheService.addCryptoInputParcel(getActivity(), mServiceIntent, inputParcel);
getActivity().setResult(RESULT_OK, mServiceIntent);
} else {
// also return passphrase back to activity
Intent returnIntent = new Intent();
- returnIntent.putExtra(MESSAGE_DATA_PASSPHRASE, passphrase);
- returnIntent.putExtra(EXTRA_KEY_ID, mSecretRing.getMasterKeyId());
- returnIntent.putExtra(EXTRA_SUBKEY_ID, mSubKeyId);
+ returnIntent.putExtra(RESULT_CRYPTO_INPUT, inputParcel);
getActivity().setResult(RESULT_OK, returnIntent);
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeViewActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeViewActivity.java
index 43af07bbe..d4858ee5d 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeViewActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeViewActivity.java
@@ -30,6 +30,7 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java
index c58a945d3..aa3c36d11 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java
@@ -39,6 +39,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
+import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.ui.util.Notify;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java
index 210960b65..442bdf8f7 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java
@@ -107,10 +107,10 @@ public class SettingsActivity extends PreferenceActivity {
values[i] = "" + valueIds[i];
}
- initializeUseDefaultYubikeyPin(
+ initializeUseDefaultYubiKeyPin(
(CheckBoxPreference) findPreference(Constants.Pref.USE_DEFAULT_YUBIKEY_PIN));
- initializeUseNumKeypadForYubikeyPin(
+ initializeUseNumKeypadForYubiKeyPin(
(CheckBoxPreference) findPreference(Constants.Pref.USE_NUMKEYPAD_FOR_YUBIKEY_PIN));
}
@@ -262,10 +262,10 @@ public class SettingsActivity extends PreferenceActivity {
values[i] = "" + valueIds[i];
}
- initializeUseDefaultYubikeyPin(
+ initializeUseDefaultYubiKeyPin(
(CheckBoxPreference) findPreference(Constants.Pref.USE_DEFAULT_YUBIKEY_PIN));
- initializeUseNumKeypadForYubikeyPin(
+ initializeUseNumKeypadForYubiKeyPin(
(CheckBoxPreference) findPreference(Constants.Pref.USE_NUMKEYPAD_FOR_YUBIKEY_PIN));
}
}
@@ -335,23 +335,23 @@ public class SettingsActivity extends PreferenceActivity {
return serverSummary + "; " + context.getString(R.string.label_preferred) + ": " + sPreferences.getPreferredKeyserver();
}
- private static void initializeUseDefaultYubikeyPin(final CheckBoxPreference mUseDefaultYubikeyPin) {
- mUseDefaultYubikeyPin.setChecked(sPreferences.useDefaultYubikeyPin());
- mUseDefaultYubikeyPin.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ private static void initializeUseDefaultYubiKeyPin(final CheckBoxPreference mUseDefaultYubiKeyPin) {
+ mUseDefaultYubiKeyPin.setChecked(sPreferences.useDefaultYubiKeyPin());
+ mUseDefaultYubiKeyPin.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
public boolean onPreferenceChange(Preference preference, Object newValue) {
- mUseDefaultYubikeyPin.setChecked((Boolean) newValue);
- sPreferences.setUseDefaultYubikeyPin((Boolean) newValue);
+ mUseDefaultYubiKeyPin.setChecked((Boolean) newValue);
+ sPreferences.setUseDefaultYubiKeyPin((Boolean) newValue);
return false;
}
});
}
- private static void initializeUseNumKeypadForYubikeyPin(final CheckBoxPreference mUseNumKeypadForYubikeyPin) {
- mUseNumKeypadForYubikeyPin.setChecked(sPreferences.useNumKeypadForYubikeyPin());
- mUseNumKeypadForYubikeyPin.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ private static void initializeUseNumKeypadForYubiKeyPin(final CheckBoxPreference mUseNumKeypadForYubiKeyPin) {
+ mUseNumKeypadForYubiKeyPin.setChecked(sPreferences.useNumKeypadForYubiKeyPin());
+ mUseNumKeypadForYubiKeyPin.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
public boolean onPreferenceChange(Preference preference, Object newValue) {
- mUseNumKeypadForYubikeyPin.setChecked((Boolean) newValue);
- sPreferences.setUseNumKeypadForYubikeyPin((Boolean) newValue);
+ mUseNumKeypadForYubiKeyPin.setChecked((Boolean) newValue);
+ sPreferences.setUseNumKeypadForYubiKeyPin((Boolean) newValue);
return false;
}
});
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java
index 080dc2495..9f2e46b38 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java
@@ -27,6 +27,7 @@ import android.view.ViewGroup;
import android.widget.TextView;
import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.ui.widget.Editor;
import org.sufficientlysecure.keychain.ui.widget.Editor.EditorListener;
import org.sufficientlysecure.keychain.ui.widget.KeyServerEditor;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java
index c518cbcdb..5c8e6bb5d 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java
@@ -36,6 +36,7 @@ import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
+import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.util.Log;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java
index a80503591..8d876ba69 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java
@@ -40,6 +40,7 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Log;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java
index ad35d2833..4ec3c28da 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java
@@ -61,18 +61,23 @@ import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
+import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.ui.linked.LinkedIdWizard;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
+import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
import org.sufficientlysecure.keychain.ui.util.Notify;
+import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener;
+import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.ui.util.QrCodeUtils;
import org.sufficientlysecure.keychain.util.ContactHelper;
import org.sufficientlysecure.keychain.util.ExportHelper;
@@ -80,15 +85,21 @@ import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.NfcHelper;
import org.sufficientlysecure.keychain.util.Preferences;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
-public class ViewKeyActivity extends BaseActivity implements
+public class ViewKeyActivity extends BaseNfcActivity implements
LoaderManager.LoaderCallbacks<Cursor> {
+ public static final String EXTRA_NFC_USER_ID = "nfc_user_id";
+ public static final String EXTRA_NFC_AID = "nfc_aid";
+ public static final String EXTRA_NFC_FINGERPRINTS = "nfc_fingerprints";
+
static final int REQUEST_QR_FINGERPRINT = 1;
static final int REQUEST_DELETE = 2;
static final int REQUEST_EXPORT = 3;
+ public static final String EXTRA_DISPLAY_RESULT = "display_result";
ExportHelper mExportHelper;
ProviderHelper mProviderHelper;
@@ -108,6 +119,8 @@ public class ViewKeyActivity extends BaseActivity implements
private ImageView mQrCode;
private CardView mQrCodeLayout;
+ private String mQrCodeLoaded;
+
// NFC
private NfcHelper mNfcHelper;
@@ -257,6 +270,21 @@ public class ViewKeyActivity extends BaseActivity implements
mNfcHelper = new NfcHelper(this, mProviderHelper);
mNfcHelper.initNfc(mDataUri);
+ if (savedInstanceState == null && getIntent().hasExtra(EXTRA_DISPLAY_RESULT)) {
+ OperationResult result = getIntent().getParcelableExtra(EXTRA_DISPLAY_RESULT);
+ result.createNotify(this).show();
+ }
+
+ startFragment(savedInstanceState, mDataUri);
+
+ if (savedInstanceState == null && getIntent().hasExtra(EXTRA_NFC_AID)) {
+ Intent intent = getIntent();
+ byte[] nfcFingerprints = intent.getByteArrayExtra(EXTRA_NFC_FINGERPRINTS);
+ String nfcUserId = intent.getStringExtra(EXTRA_NFC_USER_ID);
+ byte[] nfcAid = intent.getByteArrayExtra(EXTRA_NFC_AID);
+ showYubiKeyFragment(nfcFingerprints, nfcUserId, nfcAid);
+ }
+
}
@Override
@@ -264,6 +292,35 @@ public class ViewKeyActivity extends BaseActivity implements
setContentView(R.layout.view_key_activity);
}
+ private void startFragment(Bundle savedInstanceState, final Uri dataUri) {
+ // If we're being restored from a previous state, then we don't
+ // need to do anything and should return or else we could end
+ // up with overlapping fragments.
+ if (savedInstanceState != null) {
+ return;
+ }
+
+ new Handler().post(new Runnable() {
+ @Override
+ public void run() {
+
+ FragmentManager manager = getSupportFragmentManager();
+ if (manager.getBackStackEntryCount() == 0) {
+ // Create an instance of the fragment
+ final ViewKeyFragment frag = ViewKeyFragment.newInstance(dataUri);
+ manager.beginTransaction()
+ .replace(R.id.view_key_fragment, frag)
+ .commit();
+ manager.popBackStack();
+ } else {
+ // not sure yet if we actually want this!
+ // manager.popBackStack();
+ }
+
+ }
+ });
+ }
+
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
@@ -507,6 +564,72 @@ public class ViewKeyActivity extends BaseActivity implements
}
}
+ @Override
+ protected void onNfcPerform() throws IOException {
+
+ final byte[] nfcFingerprints = nfcGetFingerprints();
+ final String nfcUserId = nfcGetUserId();
+ final byte[] nfcAid = nfcGetAid();
+
+ String fp = KeyFormattingUtils.convertFingerprintToHex(nfcFingerprints);
+ final long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(nfcFingerprints);
+
+ if (!mFingerprint.equals(fp)) {
+ try {
+ CachedPublicKeyRing ring = mProviderHelper.getCachedPublicKeyRing(masterKeyId);
+ ring.getMasterKeyId();
+
+ Notify.create(this, R.string.snack_yubi_other, Notify.LENGTH_LONG,
+ Style.WARN, new ActionListener() {
+ @Override
+ public void onAction() {
+ Intent intent = new Intent(
+ ViewKeyActivity.this, ViewKeyActivity.class);
+ intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId));
+ intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, nfcAid);
+ intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, nfcUserId);
+ intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, nfcFingerprints);
+ startActivity(intent);
+ finish();
+ }
+ }, R.string.snack_yubikey_view).show();
+ return;
+
+ } catch (PgpKeyNotFoundException e) {
+ Notify.create(this, R.string.snack_yubi_other, Notify.LENGTH_LONG,
+ Style.WARN, new ActionListener() {
+ @Override
+ public void onAction() {
+ Intent intent = new Intent(
+ ViewKeyActivity.this, CreateKeyActivity.class);
+ intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, nfcAid);
+ intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, nfcUserId);
+ intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, nfcFingerprints);
+ startActivity(intent);
+ finish();
+ }
+ }, R.string.snack_yubikey_import).show();
+ return;
+ }
+ }
+
+ showYubiKeyFragment(nfcFingerprints, nfcUserId, nfcAid);
+
+ }
+
+ public void showYubiKeyFragment(byte[] nfcFingerprints, String nfcUserId, byte[] nfcAid) {
+ ViewKeyYubiKeyFragment frag = ViewKeyYubiKeyFragment.newInstance(
+ nfcFingerprints, nfcUserId, nfcAid);
+
+ FragmentManager manager = getSupportFragmentManager();
+
+ manager.popBackStack("yubikey", FragmentManager.POP_BACK_STACK_INCLUSIVE);
+ manager.beginTransaction()
+ .addToBackStack("yubikey")
+ .replace(R.id.view_key_fragment, frag)
+ .commit();
+ }
+
private void encrypt(Uri dataUri, boolean text) {
// If there is no encryption key, don't bother.
if (!mHasEncrypt) {
@@ -638,6 +761,7 @@ public class ViewKeyActivity extends BaseActivity implements
}
protected void onPostExecute(Bitmap qrCode) {
+ mQrCodeLoaded = fingerprint;
// scale the image up to our actual size. we do this in code rather
// than let the ImageView do this because we don't require filtering.
Bitmap scaled = Bitmap.createScaledBitmap(qrCode,
@@ -705,25 +829,13 @@ public class ViewKeyActivity extends BaseActivity implements
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
switch (loader.getId()) {
+
case LOADER_ID_UNIFIED: {
- // if there is no data, just break
+
if (!data.moveToFirst()) {
- break;
+ return;
}
- String oldFingerprint = mFingerprint;
- mMasterKeyId = data.getLong(INDEX_MASTER_KEY_ID);
- byte[] fpData = data.getBlob(INDEX_FINGERPRINT);
- mFingerprint = KeyFormattingUtils.convertFingerprintToHex(fpData);
-
- mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0;
- mHasEncrypt = data.getInt(INDEX_HAS_ENCRYPT) != 0;
- mIsRevoked = data.getInt(INDEX_IS_REVOKED) > 0;
- mIsExpired = data.getInt(INDEX_IS_EXPIRED) != 0;
- mIsVerified = data.getInt(INDEX_VERIFIED) > 0;
-
- startFragment(mIsSecret, fpData);
-
// get name, email, and comment from USER_ID
KeyRing.UserId mainUserId = KeyRing.splitUserId(data.getString(INDEX_USER_ID));
if (mainUserId.name != null) {
@@ -732,6 +844,15 @@ public class ViewKeyActivity extends BaseActivity implements
mName.setText(R.string.user_id_no_name);
}
+ mMasterKeyId = data.getLong(INDEX_MASTER_KEY_ID);
+ mFingerprint = KeyFormattingUtils.convertFingerprintToHex(data.getBlob(INDEX_FINGERPRINT));
+
+ mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0;
+ mHasEncrypt = data.getInt(INDEX_HAS_ENCRYPT) != 0;
+ mIsRevoked = data.getInt(INDEX_IS_REVOKED) > 0;
+ mIsExpired = data.getInt(INDEX_IS_EXPIRED) != 0;
+ mIsVerified = data.getInt(INDEX_VERIFIED) > 0;
+
// if the refresh animation isn't playing
if (!mRotate.hasStarted() && !mRotateSpin.hasStarted()) {
// re-create options menu based on mIsSecret, mIsVerified
@@ -768,7 +889,6 @@ public class ViewKeyActivity extends BaseActivity implements
} else if (mIsExpired) {
if (mIsSecret) {
mStatusText.setText(R.string.view_key_expired_secret);
- mName.setText(mainUserId.name);
} else {
mStatusText.setText(R.string.view_key_expired);
}
@@ -787,7 +907,7 @@ public class ViewKeyActivity extends BaseActivity implements
mStatusImage.setVisibility(View.GONE);
color = getResources().getColor(R.color.primary);
// reload qr code only if the fingerprint changed
- if (!mFingerprint.equals(oldFingerprint)) {
+ if (!mFingerprint.equals(mQrCodeLoaded)) {
loadQrCode(mFingerprint);
}
photoTask.execute(mMasterKeyId);
@@ -873,6 +993,7 @@ public class ViewKeyActivity extends BaseActivity implements
mStatusImage.setAlpha(80);
break;
+
}
}
}
@@ -882,27 +1003,4 @@ public class ViewKeyActivity extends BaseActivity implements
}
- private void startFragment(final boolean isSecret, final byte[] fingerprint) {
- new Handler().post(new Runnable() {
- @Override
- public void run() {
-
- FragmentManager manager = getSupportFragmentManager();
- if (manager.getBackStackEntryCount() == 0) {
- // Create an instance of the fragment
- final ViewKeyFragment frag = ViewKeyFragment.newInstance(
- mDataUri, isSecret, fingerprint);
- manager.beginTransaction()
- .replace(R.id.view_key_fragment, frag)
- .commit();
- manager.popBackStack();
- } else {
- // not sure yet if we actually want this!
- // manager.popBackStack();
- }
-
- }
- });
- }
-
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java
index f17d6e0fd..9e8a12c8a 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java
@@ -38,6 +38,7 @@ import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
+import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.ContactHelper;
import org.sufficientlysecure.keychain.util.ExportHelper;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java
index 8610b2fee..046de6c0c 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java
@@ -30,6 +30,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.provider.ContactsContract;
import android.support.v4.app.LoaderManager;
+import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v7.widget.CardView;
import android.transition.Fade;
@@ -48,11 +49,11 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
import org.sufficientlysecure.keychain.ui.adapter.LinkedIdsAdapter;
+import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;
import org.sufficientlysecure.keychain.ui.dialog.UserIdInfoDialogFragment;
import org.sufficientlysecure.keychain.ui.linked.LinkedIdViewFragment;
import org.sufficientlysecure.keychain.ui.linked.LinkedIdViewFragment.OnIdentityLoadedListener;
-import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.ContactHelper;
import org.sufficientlysecure.keychain.util.Log;
@@ -62,21 +63,26 @@ public class ViewKeyFragment extends LoaderFragment implements
LoaderManager.LoaderCallbacks<Cursor> {
public static final String ARG_DATA_URI = "uri";
- private static final String ARG_FINGERPRINT = "fingerprint";
- private static final String ARG_IS_SECRET = "is_secret";
private ListView mUserIds;
//private ListView mLinkedSystemContact;
boolean mIsSecret = false;
- boolean mSystemContactLoaded = false;
+ CardView mSystemContactCard;
LinearLayout mSystemContactLayout;
ImageView mSystemContactPicture;
TextView mSystemContactName;
- private static final int LOADER_ID_USER_IDS = 0;
- private static final int LOADER_ID_LINKED_IDS = 1;
+ private static final int LOADER_ID_UNIFIED = 0;
+ private static final int LOADER_ID_USER_IDS = 1;
+ private static final int LOADER_ID_LINKED_CONTACT = 2;
+ private static final int LOADER_ID_LINKED_IDS = 3;
+
+ private static final String LOADER_EXTRA_LINKED_CONTACT_MASTER_KEY_ID
+ = "loader_linked_contact_master_key_id";
+ private static final String LOADER_EXTRA_LINKED_CONTACT_IS_SECRET
+ = "loader_linked_contact_is_secret";
private UserIdsAdapter mUserIdsAdapter;
private LinkedIdsAdapter mLinkedIdsAdapter;
@@ -90,12 +96,10 @@ public class ViewKeyFragment extends LoaderFragment implements
/**
* Creates new instance of this fragment
*/
- public static ViewKeyFragment newInstance(Uri dataUri, boolean isSecret, byte[] fingerprint) {
+ public static ViewKeyFragment newInstance(Uri dataUri) {
ViewKeyFragment frag = new ViewKeyFragment();
Bundle args = new Bundle();
args.putParcelable(ARG_DATA_URI, dataUri);
- args.putBoolean(ARG_IS_SECRET, isSecret);
- args.putByteArray(ARG_FINGERPRINT, fingerprint);
frag.setArguments(args);
@@ -103,23 +107,6 @@ public class ViewKeyFragment extends LoaderFragment implements
}
@Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
-
- Bundle args = getArguments();
- Uri dataUri = args.getParcelable(ARG_DATA_URI);
- if (dataUri == null) {
- Log.e(Constants.TAG, "Data missing. Should be Uri of key!");
- getActivity().finish();
- return;
- }
- boolean isSecret = args.getBoolean(ARG_IS_SECRET);
- byte[] fingerprint = args.getByteArray(ARG_FINGERPRINT);
-
- loadData(dataUri, isSecret, fingerprint);
- }
-
- @Override
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
View root = super.onCreateView(inflater, superContainer, savedInstanceState);
View view = inflater.inflate(R.layout.view_key_fragment, getContainer());
@@ -144,6 +131,7 @@ public class ViewKeyFragment extends LoaderFragment implements
}
});
+ mSystemContactCard = (CardView) view.findViewById(R.id.linked_system_contact_card);
mSystemContactLayout = (LinearLayout) view.findViewById(R.id.system_contact_layout);
mSystemContactName = (TextView) view.findViewById(R.id.system_contact_name);
mSystemContactPicture = (ImageView) view.findViewById(R.id.system_contact_picture);
@@ -209,60 +197,64 @@ public class ViewKeyFragment extends LoaderFragment implements
}
/**
- * Checks if a system contact exists for given masterKeyId, and if it does, sets name, picture
- * and onClickListener for the linked system contact's layout
- * In the case of a secret key, "me" contact details are loaded
- *
- * @param masterKeyId
+ * Hides card if no linked system contact exists. Sets name, picture
+ * and onClickListener for the linked system contact's layout.
+ * In the case of a secret key, "me" (own profile) contact details are loaded.
*/
- private void loadLinkedSystemContact(final long masterKeyId) {
+ private void loadLinkedSystemContact(final long contactId) {
+
final Context context = mSystemContactName.getContext();
final ContentResolver resolver = context.getContentResolver();
- long contactId;
String contactName = null;
if (mIsSecret) {//all secret keys are linked to "me" profile in contacts
- contactId = ContactHelper.getMainProfileContactId(resolver);
List<String> mainProfileNames = ContactHelper.getMainProfileContactName(context);
if (mainProfileNames != null && mainProfileNames.size() > 0) {
contactName = mainProfileNames.get(0);
}
-
} else {
- contactId = ContactHelper.findContactId(resolver, masterKeyId);
contactName = ContactHelper.getContactName(resolver, contactId);
}
if (contactName != null) {//contact name exists for given master key
+ showLinkedSystemContact();
+
mSystemContactName.setText(contactName);
Bitmap picture;
if (mIsSecret) {
picture = ContactHelper.loadMainProfilePhoto(resolver, false);
} else {
- picture = ContactHelper.loadPhotoByMasterKeyId(resolver, masterKeyId, false);
+ picture = ContactHelper.loadPhotoByContactId(resolver, contactId, false);
}
if (picture != null) mSystemContactPicture.setImageBitmap(picture);
- final long finalContactId = contactId;
mSystemContactLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- launchContactActivity(finalContactId, context);
+ launchContactActivity(contactId, context);
}
});
- mSystemContactLoaded = true;
+ } else {
+ hideLinkedSystemContact();
}
}
+ private void hideLinkedSystemContact() {
+ mSystemContactCard.setVisibility(View.GONE);
+ }
+
+ private void showLinkedSystemContact() {
+ mSystemContactCard.setVisibility(View.VISIBLE);
+ }
+
/**
* launches the default android Contacts app to view a contact with the passed
* contactId (CONTACT_ID column from ContactsContract.RawContact table which is _ID column in
* ContactsContract.Contact table)
*
* @param contactId _ID for row in ContactsContract.Contacts table
- * @param context
*/
private void launchContactActivity(final long contactId, Context context) {
Intent intent = new Intent(Intent.ACTION_VIEW);
@@ -271,23 +263,61 @@ public class ViewKeyFragment extends LoaderFragment implements
context.startActivity(intent);
}
- private void loadData(Uri dataUri, boolean isSecret, byte[] fingerprint) {
- mDataUri = dataUri;
- mIsSecret = isSecret;
- mFingerprint = fingerprint;
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
- Log.i(Constants.TAG, "mDataUri: " + mDataUri);
+ Uri dataUri = getArguments().getParcelable(ARG_DATA_URI);
+ if (dataUri == null) {
+ Log.e(Constants.TAG, "Data missing. Should be Uri of key!");
+ getActivity().finish();
+ return;
+ }
+
+ loadData(dataUri);
+ }
- // load user ids after we know if it's a secret key
- mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0, !mIsSecret, null);
- mUserIds.setAdapter(mUserIdsAdapter);
- getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this);
+ // These are the rows that we will retrieve.
+ static final String[] UNIFIED_PROJECTION = new String[]{
+ KeychainContract.KeyRings._ID,
+ KeychainContract.KeyRings.MASTER_KEY_ID,
+ KeychainContract.KeyRings.USER_ID,
+ KeychainContract.KeyRings.IS_REVOKED,
+ KeychainContract.KeyRings.IS_EXPIRED,
+ KeychainContract.KeyRings.VERIFIED,
+ KeychainContract.KeyRings.HAS_ANY_SECRET,
+ KeychainContract.KeyRings.FINGERPRINT,
+ KeychainContract.KeyRings.HAS_ENCRYPT
+ };
+
+ static final int INDEX_MASTER_KEY_ID = 1;
+ @SuppressWarnings("unused")
+ static final int INDEX_USER_ID = 2;
+ @SuppressWarnings("unused")
+ static final int INDEX_IS_REVOKED = 3;
+ @SuppressWarnings("unused")
+ static final int INDEX_IS_EXPIRED = 4;
+ @SuppressWarnings("unused")
+ static final int INDEX_VERIFIED = 5;
+ static final int INDEX_HAS_ANY_SECRET = 6;
+ static final int INDEX_FINGERPRINT = 7;
+ @SuppressWarnings("unused")
+ static final int INDEX_HAS_ENCRYPT = 8;
+
+ private static final String[] RAWCONTACT_PROJECTION = {
+ ContactsContract.RawContacts.CONTACT_ID
+ };
+
+ private static final int INDEX_CONTACT_ID = 0;
+
+ private void loadData(Uri dataUri) {
+ mDataUri = dataUri;
- mLinkedIdsAdapter = new LinkedIdsAdapter(getActivity(), null, 0,
- mIsSecret, mLinkedIdsExpander);
- mLinkedIds.setAdapter(mLinkedIdsAdapter);
- getLoaderManager().initLoader(LOADER_ID_LINKED_IDS, null, this);
+ Log.i(Constants.TAG, "mDataUri: " + mDataUri);
+ // Prepare the loaders. Either re-connect with an existing ones,
+ // or start new ones.
+ getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this);
}
@Override
@@ -295,11 +325,45 @@ public class ViewKeyFragment extends LoaderFragment implements
setContentShown(false);
switch (id) {
- case LOADER_ID_USER_IDS:
+ case LOADER_ID_UNIFIED: {
+ Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri);
+ return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null);
+ }
+
+ case LOADER_ID_USER_IDS: {
return UserIdsAdapter.createLoader(getActivity(), mDataUri);
+ }
- case LOADER_ID_LINKED_IDS:
+ case LOADER_ID_LINKED_IDS: {
return LinkedIdsAdapter.createLoader(getActivity(), mDataUri);
+ }
+
+ //we need a separate loader for linked contact to ensure refreshing on verification
+ case LOADER_ID_LINKED_CONTACT: {
+ //passed in args to explicitly specify their need
+ long masterKeyId = args.getLong(LOADER_EXTRA_LINKED_CONTACT_MASTER_KEY_ID);
+ boolean isSecret = args.getBoolean(LOADER_EXTRA_LINKED_CONTACT_IS_SECRET);
+
+ Uri baseUri;
+ if (isSecret)
+ baseUri = ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI;
+ else
+ baseUri = ContactsContract.RawContacts.CONTENT_URI;
+
+ return new CursorLoader(
+ getActivity(),
+ baseUri,
+ RAWCONTACT_PROJECTION,
+ ContactsContract.RawContacts.ACCOUNT_TYPE + "=? AND " +
+ ContactsContract.RawContacts.SOURCE_ID + "=? AND " +
+ ContactsContract.RawContacts.DELETED + "=?",
+ new String[]{//"0" for "not deleted"
+ Constants.ACCOUNT_TYPE,
+ Long.toString(masterKeyId),
+ "0"
+ },
+ null);
+ }
default:
return null;
@@ -307,21 +371,71 @@ public class ViewKeyFragment extends LoaderFragment implements
}
@Override
- public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
+ public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+ /* TODO better error handling? May cause problems when a key is deleted,
+ * because the notification triggers faster than the activity closes.
+ */
+ // Avoid NullPointerExceptions...
+ if (data.getCount() == 0) {
+ return;
+ }
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
switch (loader.getId()) {
+ case LOADER_ID_UNIFIED: {
+ if (data.moveToFirst()) {
+
+ mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0;
+ mFingerprint = data.getBlob(INDEX_FINGERPRINT);
+
+ // load user ids after we know if it's a secret key
+ mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0, !mIsSecret, null);
+ mUserIds.setAdapter(mUserIdsAdapter);
+ getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this);
+
+ mLinkedIdsAdapter =
+ new LinkedIdsAdapter(getActivity(), null, 0, mIsSecret, mLinkedIdsExpander);
+ mLinkedIds.setAdapter(mLinkedIdsAdapter);
+ getLoaderManager().initLoader(LOADER_ID_LINKED_IDS, null, this);
+
+ long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID);
+ // we need to load linked contact here to prevent lag introduced by loader
+ // for the linked contact
+ long contactId = ContactHelper.findContactId(
+ getActivity().getContentResolver(),
+ masterKeyId);
+ loadLinkedSystemContact(contactId);
+
+ Bundle linkedContactData = new Bundle();
+ linkedContactData.putLong(LOADER_EXTRA_LINKED_CONTACT_MASTER_KEY_ID, masterKeyId);
+ linkedContactData.putBoolean(LOADER_EXTRA_LINKED_CONTACT_IS_SECRET, mIsSecret);
+
+ // initialises loader for contact query so we can listen to any updates
+ getLoaderManager().initLoader(LOADER_ID_LINKED_CONTACT, linkedContactData, this);
+
+ break;
+ }
+ }
+
case LOADER_ID_USER_IDS: {
- mUserIdsAdapter.swapCursor(cursor);
- loadLinkedSystemContact(KeyFormattingUtils.convertFingerprintToKeyId(mFingerprint));
+ mUserIdsAdapter.swapCursor(data);
break;
}
case LOADER_ID_LINKED_IDS: {
- mLinkedIdsCard.setVisibility(cursor.getCount() > 0 ? View.VISIBLE : View.GONE);
- mLinkedIdsAdapter.swapCursor(cursor);
+ mLinkedIdsCard.setVisibility(data.getCount() > 0 ? View.VISIBLE : View.GONE);
+ mLinkedIdsAdapter.swapCursor(data);
+ break;
+ }
+
+ case LOADER_ID_LINKED_CONTACT: {
+ if (data.moveToFirst()) {// if we have a linked contact
+ long contactId = data.getLong(INDEX_CONTACT_ID);
+ loadLinkedSystemContact(contactId);
+ }
break;
}
+
}
setContentShown(true);
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubiKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubiKeyFragment.java
new file mode 100644
index 000000000..812874456
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubiKeyFragment.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2015 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.ui;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+import android.content.Intent;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.Messenger;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.LoaderManager.LoaderCallbacks;
+import android.support.v4.content.CursorLoader;
+import android.support.v4.content.Loader;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TextView;
+
+import org.spongycastle.util.encoders.Hex;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
+import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult;
+import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
+import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
+import org.sufficientlysecure.keychain.service.KeychainIntentService;
+import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
+import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
+
+public class ViewKeyYubiKeyFragment extends Fragment
+ implements LoaderCallbacks<Cursor> {
+
+ public static final String ARG_FINGERPRINT = "fingerprint";
+ public static final String ARG_USER_ID = "user_id";
+ public static final String ARG_CARD_AID = "aid";
+ private byte[][] mFingerprints;
+ private String mUserId;
+ private byte[] mCardAid;
+ private long mMasterKeyId;
+ private Button vButton;
+ private TextView vStatus;
+
+ public static ViewKeyYubiKeyFragment newInstance(byte[] fingerprints, String userId, byte[] aid) {
+ ViewKeyYubiKeyFragment frag = new ViewKeyYubiKeyFragment();
+
+ Bundle args = new Bundle();
+ args.putByteArray(ARG_FINGERPRINT, fingerprints);
+ args.putString(ARG_USER_ID, userId);
+ args.putByteArray(ARG_CARD_AID, aid);
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Bundle args = getArguments();
+ ByteBuffer buf = ByteBuffer.wrap(args.getByteArray(ARG_FINGERPRINT));
+ mFingerprints = new byte[buf.remaining()/40][];
+ for (int i = 0; i < mFingerprints.length; i++) {
+ mFingerprints[i] = new byte[20];
+ buf.get(mFingerprints[i]);
+ }
+ mUserId = args.getString(ARG_USER_ID);
+ mCardAid = args.getByteArray(ARG_CARD_AID);
+
+ mMasterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mFingerprints[0]);
+
+ getLoaderManager().initLoader(0, null, this);
+
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.view_key_yubikey, null);
+
+ TextView vSerNo = (TextView) view.findViewById(R.id.yubikey_serno);
+ TextView vUserId = (TextView) view.findViewById(R.id.yubikey_userid);
+
+ String serno = Hex.toHexString(mCardAid, 10, 4);
+ vSerNo.setText(getString(R.string.yubikey_serno, serno));
+
+ if (!mUserId.isEmpty()) {
+ vUserId.setText(getString(R.string.yubikey_key_holder, mUserId));
+ } else {
+ vUserId.setText(getString(R.string.yubikey_key_holder_unset));
+ }
+
+ vButton = (Button) view.findViewById(R.id.button_bind);
+ vButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ promoteToSecretKey();
+ }
+ });
+
+ vStatus = (TextView) view.findViewById(R.id.yubikey_status);
+
+ return view;
+ }
+
+ public void promoteToSecretKey() {
+
+ ServiceProgressHandler saveHandler = new ServiceProgressHandler(getActivity()) {
+ public void handleMessage(Message message) {
+ // handle messages by standard KeychainIntentServiceHandler first
+ super.handleMessage(message);
+
+ if (message.arg1 == MessageStatus.OKAY.ordinal()) {
+ // get returned data bundle
+ Bundle returnData = message.getData();
+
+ PromoteKeyResult result =
+ returnData.getParcelable(DecryptVerifyResult.EXTRA_RESULT);
+
+ result.createNotify(getActivity()).show();
+ }
+
+ }
+ };
+
+ // Send all information needed to service to decrypt in other thread
+ Intent intent = new Intent(getActivity(), KeychainIntentService.class);
+
+ // fill values for this action
+
+ intent.setAction(KeychainIntentService.ACTION_PROMOTE_KEYRING);
+
+ Bundle data = new Bundle();
+ data.putLong(KeychainIntentService.PROMOTE_MASTER_KEY_ID, mMasterKeyId);
+ data.putByteArray(KeychainIntentService.PROMOTE_CARD_AID, mCardAid);
+ intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
+
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(saveHandler);
+ intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
+
+ // start service with intent
+ getActivity().startService(intent);
+
+ }
+
+ public static final String[] PROJECTION = new String[]{
+ Keys._ID,
+ Keys.KEY_ID,
+ Keys.RANK,
+ Keys.HAS_SECRET,
+ Keys.FINGERPRINT
+ };
+ private static final int INDEX_KEY_ID = 1;
+ private static final int INDEX_RANK = 2;
+ private static final int INDEX_HAS_SECRET = 3;
+ private static final int INDEX_FINGERPRINT = 4;
+
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ return new CursorLoader(getActivity(), Keys.buildKeysUri(mMasterKeyId),
+ PROJECTION, null, null, null);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+ if (!data.moveToFirst()) {
+ // wut?
+ return;
+ }
+
+ boolean allBound = true;
+ boolean noneBound = true;
+
+ do {
+ SecretKeyType keyType = SecretKeyType.fromNum(data.getInt(INDEX_HAS_SECRET));
+ byte[] fingerprint = data.getBlob(INDEX_FINGERPRINT);
+ Integer index = naiveIndexOf(mFingerprints, fingerprint);
+ if (index == null) {
+ continue;
+ }
+ if (keyType == SecretKeyType.DIVERT_TO_CARD) {
+ noneBound = false;
+ } else {
+ allBound = false;
+ }
+ } while (data.moveToNext());
+
+ if (allBound) {
+ vButton.setVisibility(View.GONE);
+ vStatus.setText(R.string.yubikey_status_bound);
+ } else {
+ vButton.setVisibility(View.VISIBLE);
+ vStatus.setText(noneBound
+ ? R.string.yubikey_status_unbound
+ : R.string.yubikey_status_partly);
+ }
+
+ }
+
+ public Integer naiveIndexOf(byte[][] haystack, byte[] needle) {
+ for (int i = 0; i < haystack.length; i++) {
+ if (Arrays.equals(needle, haystack[i])) {
+ return i;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> loader) {
+
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java
index 9304b14f1..6f19fc6ed 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java
@@ -147,6 +147,8 @@ public class KeyAdapter extends CursorAdapter {
mStatus.setVisibility(View.GONE);
if (mSlingerButton.hasOnClickListeners()) {
mSlinger.setVisibility(View.VISIBLE);
+ } else {
+ mSlinger.setVisibility(View.GONE);
}
mMainUserId.setTextColor(context.getResources().getColor(R.color.black));
mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.black));
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SpacesItemDecoration.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SpacesItemDecoration.java
new file mode 100644
index 000000000..bc595803b
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SpacesItemDecoration.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2015 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.ui.adapter;
+
+import android.graphics.Rect;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+
+/**
+ * https://gist.github.com/yrom/3b4bcbc2370ca2290434
+ */
+public class SpacesItemDecoration extends RecyclerView.ItemDecoration {
+ private int space;
+ private int spanCount;
+ private int lastItemInFirstLane = -1;
+
+ public SpacesItemDecoration(int space) {
+ this(space, 1);
+ }
+
+ /**
+ * @param space
+ * @param spanCount spans count of one lane
+ */
+ public SpacesItemDecoration(int space, int spanCount) {
+ this.space = space;
+ this.spanCount = spanCount;
+ }
+
+ @Override
+ public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
+ RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
+ final int position = params.getViewPosition();
+ final int spanSize;
+ final int index;
+ if (params instanceof GridLayoutManager.LayoutParams) {
+ GridLayoutManager.LayoutParams gridParams = (GridLayoutManager.LayoutParams) params;
+ spanSize = gridParams.getSpanSize();
+ index = gridParams.getSpanIndex();
+ } else {
+ spanSize = 1;
+ index = position % spanCount;
+ }
+ // invalid value
+ if (spanSize < 1 || index < 0) return;
+
+ if (spanSize == spanCount) { // full span
+ outRect.left = space;
+ outRect.right = space;
+ } else {
+ if (index == 0) { // left one
+ outRect.left = space;
+ }
+ // spanCount >= 1
+ if (index == spanCount - 1) { // right one
+ outRect.right = space;
+ }
+ if (outRect.left == 0) {
+ outRect.left = space / 2;
+ }
+ if (outRect.right == 0) {
+ outRect.right = space / 2;
+ }
+ }
+ // set top to all in first lane
+ if (position < spanCount && spanSize <= spanCount) {
+ if (lastItemInFirstLane < 0) { // lay out at first time
+ lastItemInFirstLane = position + spanSize == spanCount ? position : lastItemInFirstLane;
+ outRect.top = space;
+ } else if (position <= lastItemInFirstLane) { // scroll to first lane again
+ outRect.top = space;
+ }
+ }
+ outRect.bottom = space;
+
+ }
+} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BaseActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java
index 41fa50705..07d2ef8c0 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BaseActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java
@@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-package org.sufficientlysecure.keychain.ui;
+package org.sufficientlysecure.keychain.ui.base;
import android.app.Activity;
import android.os.Bundle;
@@ -63,8 +63,8 @@ public abstract class BaseActivity extends ActionBarActivity {
* Inflate custom design to look like a full screen dialog, as specified in Material Design Guidelines
* see http://www.google.com/design/spec/components/dialogs.html#dialogs-full-screen-dialogs
*/
- protected void setFullScreenDialogDoneClose(int doneText, View.OnClickListener doneOnClickListener,
- View.OnClickListener cancelOnClickListener) {
+ public void setFullScreenDialogDoneClose(int doneText, View.OnClickListener doneOnClickListener,
+ View.OnClickListener cancelOnClickListener) {
setActionBarIcon(R.drawable.ic_close_white_24dp);
// Inflate the custom action bar view
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java
index 7311f4879..95c1690b1 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java
@@ -1,131 +1,105 @@
-/**
- * Copyright (c) 2013-2014 Philipp Jakubeit, Signe Rüsch, Dominik Schürmann
+/*
+ * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
*
- * Licensed under the Bouncy Castle License (MIT license). See LICENSE file for details.
+ * 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.ui;
+package org.sufficientlysecure.keychain.ui.base;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
-import android.annotation.TargetApi;
+import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentFilter;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.IsoDep;
-import android.os.Build;
import android.os.Bundle;
-import android.view.WindowManager;
import android.widget.Toast;
import org.spongycastle.bcpg.HashAlgorithmTags;
import org.spongycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
+import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
+import org.sufficientlysecure.keychain.ui.CreateKeyActivity;
+import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity;
+import org.sufficientlysecure.keychain.ui.ViewKeyActivity;
+import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
+import org.sufficientlysecure.keychain.ui.util.Notify;
+import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.util.Iso7816TLV;
import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.Passphrase;
+import org.sufficientlysecure.keychain.util.Preferences;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-
-/**
- * This class provides a communication interface to OpenPGP applications on ISO SmartCard compliant
- * NFC devices.
- *
- * For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf
- */
-@TargetApi(Build.VERSION_CODES.GINGERBREAD_MR1)
-public class NfcActivity extends BaseActivity {
-
- // actions
- public static final String ACTION_SIGN_HASH = "sign_hash";
- public static final String ACTION_DECRYPT_SESSION_KEY = "decrypt_session_key";
-
- // always
- public static final String EXTRA_KEY_ID = "key_id";
- public static final String EXTRA_PIN = "pin";
- // special extra for OpenPgpService
- public static final String EXTRA_DATA = "data";
+public abstract class BaseNfcActivity extends BaseActivity {
- // sign
- public static final String EXTRA_NFC_HASH_TO_SIGN = "nfc_hash";
- public static final String EXTRA_NFC_HASH_ALGO = "nfc_hash_algo";
-
- // decrypt
- public static final String EXTRA_NFC_ENC_SESSION_KEY = "encrypted_session_key";
-
- private Intent mServiceIntent;
-
- private static final int TIMEOUT = 100000;
+ public static final int REQUEST_CODE_PASSPHRASE = 1;
+ protected Passphrase mPin;
private NfcAdapter mNfcAdapter;
private IsoDep mIsoDep;
- private String mAction;
- private String mPin;
- private Long mKeyId;
-
- // sign
- private byte[] mHashToSign;
- private int mHashAlgo;
-
- // decrypt
- private byte[] mEncryptedSessionKey;
+ private static final int TIMEOUT = 100000;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- Log.d(Constants.TAG, "NfcActivity.onCreate");
-
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
Intent intent = getIntent();
- Bundle data = intent.getExtras();
String action = intent.getAction();
-
- // if we get are passed a key id, save it for the check
- if (data.containsKey(EXTRA_KEY_ID)) {
- mKeyId = data.getLong(EXTRA_KEY_ID);
+ if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) {
+ throw new AssertionError("should not happen: NfcOperationActivity.onCreate is called instead of onNewIntent!");
}
- switch (action) {
- case ACTION_SIGN_HASH:
- mAction = action;
- mPin = data.getString(EXTRA_PIN);
- mHashToSign = data.getByteArray(EXTRA_NFC_HASH_TO_SIGN);
- mHashAlgo = data.getInt(EXTRA_NFC_HASH_ALGO);
- mServiceIntent = data.getParcelable(EXTRA_DATA);
-
- Log.d(Constants.TAG, "NfcActivity mAction: " + mAction);
- Log.d(Constants.TAG, "NfcActivity mPin: " + mPin);
- Log.d(Constants.TAG, "NfcActivity mHashToSign as hex: " + getHex(mHashToSign));
- Log.d(Constants.TAG, "NfcActivity mServiceIntent: " + mServiceIntent.toString());
- break;
- case ACTION_DECRYPT_SESSION_KEY:
- mAction = action;
- mPin = data.getString(EXTRA_PIN);
- mEncryptedSessionKey = data.getByteArray(EXTRA_NFC_ENC_SESSION_KEY);
- mServiceIntent = data.getParcelable(EXTRA_DATA);
-
- Log.d(Constants.TAG, "NfcActivity mAction: " + mAction);
- Log.d(Constants.TAG, "NfcActivity mPin: " + mPin);
- Log.d(Constants.TAG, "NfcActivity mEncryptedSessionKey as hex: " + getHex(mEncryptedSessionKey));
- Log.d(Constants.TAG, "NfcActivity mServiceIntent: " + mServiceIntent.toString());
- break;
- case NfcAdapter.ACTION_TAG_DISCOVERED:
- Log.e(Constants.TAG, "This should not happen! NfcActivity.onCreate() is being called instead of onNewIntent()!");
- toast("This should not happen! Please create a new bug report that the NFC screen is restarted!");
- finish();
- break;
- default:
- Log.d(Constants.TAG, "Action not supported: " + action);
- break;
- }
}
+ /**
+ * This activity is started as a singleTop activity.
+ * All new NFC Intents which are delivered to this activity are handled here
+ */
@Override
- protected void initLayout() {
- setContentView(R.layout.nfc_activity);
+ public void onNewIntent(Intent intent) {
+ if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
+ try {
+ handleNdefDiscoveredIntent(intent);
+ } catch (IOException e) {
+ handleNfcError(e);
+ }
+ }
+ }
+
+ public void handleNfcError(IOException e) {
+
+ Log.e(Constants.TAG, "nfc error", e);
+ Notify.create(this, getString(R.string.error_nfc, e.getMessage()), Style.WARN).show();
+
+ }
+
+ public void handlePinError() {
+ toast("Wrong PIN!");
+ setResult(RESULT_CANCELED);
+ finish();
}
/**
@@ -134,7 +108,7 @@ public class NfcActivity extends BaseActivity {
*/
public void onPause() {
super.onPause();
- Log.d(Constants.TAG, "NfcActivity.onPause");
+ Log.d(Constants.TAG, "BaseNfcActivity.onPause");
disableNfcForegroundDispatch();
}
@@ -145,25 +119,45 @@ public class NfcActivity extends BaseActivity {
*/
public void onResume() {
super.onResume();
- Log.d(Constants.TAG, "NfcActivity.onResume");
+ Log.d(Constants.TAG, "BaseNfcActivity.onResume");
enableNfcForegroundDispatch();
}
- /**
- * This activity is started as a singleTop activity.
- * All new NFC Intents which are delivered to this activity are handled here
- */
- public void onNewIntent(Intent intent) {
- if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
- try {
- handleNdefDiscoveredIntent(intent);
- } catch (IOException e) {
- Log.e(Constants.TAG, "Connection error!", e);
- toast("Connection Error: " + e.getMessage());
- setResult(RESULT_CANCELED, mServiceIntent);
- finish();
- }
+ protected void obtainYubiKeyPin(RequiredInputParcel requiredInput) {
+
+ Preferences prefs = Preferences.getPreferences(this);
+ if (prefs.useDefaultYubiKeyPin()) {
+ mPin = new Passphrase("123456");
+ return;
+ }
+
+ Intent intent = new Intent(this, PassphraseDialogActivity.class);
+ intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT,
+ RequiredInputParcel.createRequiredPassphrase(requiredInput));
+ startActivityForResult(intent, REQUEST_CODE_PASSPHRASE);
+
+ }
+
+ protected void setYubiKeyPin(Passphrase pin) {
+ mPin = pin;
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ case REQUEST_CODE_PASSPHRASE:
+ if (resultCode != Activity.RESULT_OK) {
+ setResult(resultCode);
+ finish();
+ return;
+ }
+ CryptoInputParcel input = data.getParcelableExtra(PassphraseDialogActivity.RESULT_CRYPTO_INPUT);
+ mPin = input.getPassphrase();
+ break;
+
+ default:
+ super.onActivityResult(requestCode, resultCode, data);
}
}
@@ -181,7 +175,7 @@ public class NfcActivity extends BaseActivity {
* on ISO SmartCard Systems specification.
*
*/
- private void handleNdefDiscoveredIntent(Intent intent) throws IOException {
+ protected void handleNdefDiscoveredIntent(Intent intent) throws IOException {
Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
@@ -196,113 +190,64 @@ public class NfcActivity extends BaseActivity {
// Command APDU (page 51) for SELECT FILE command (page 29)
String opening =
- "00" // CLA
- + "A4" // INS
- + "04" // P1
- + "00" // P2
- + "06" // Lc (number of bytes)
- + "D27600012401" // Data (6 bytes)
- + "00"; // Le
- if ( ! card(opening).equals(accepted)) { // activate connection
- toast("Opening Error!");
- setResult(RESULT_CANCELED, mServiceIntent);
- finish();
- return;
+ "00" // CLA
+ + "A4" // INS
+ + "04" // P1
+ + "00" // P2
+ + "06" // Lc (number of bytes)
+ + "D27600012401" // Data (6 bytes)
+ + "00"; // Le
+ if ( ! nfcCommunicate(opening).equals(accepted)) { // activate connection
+ throw new IOException("Initialization failed!");
}
- // Command APDU for VERIFY command (page 32)
- String login =
- "00" // CLA
- + "20" // INS
- + "00" // P1
- + "82" // P2 (PW1)
- + String.format("%02x", mPin.length()) // Lc
- + Hex.toHexString(mPin.getBytes());
- if ( ! card(login).equals(accepted)) { // login
- toast("Wrong PIN!");
- setResult(RESULT_CANCELED, mServiceIntent);
- finish();
- return;
- }
+ onNfcPerform();
- if (ACTION_SIGN_HASH.equals(mAction)) {
+ mIsoDep.close();
+ mIsoDep = null;
- // If we were supplied with a key id for checking, do so
- if (mKeyId != null) {
- // For signing, we check the master key
- long keyId = nfcGetKeyId(mIsoDep, 0);
- // If it's wrong, just cancel
- if (keyId != mKeyId) {
- toast("NFC Tag has wrong signing key id!");
- setResult(RESULT_CANCELED, mServiceIntent);
- finish();
- return;
- }
- }
+ }
- // returns signed hash
- byte[] signedHash = nfcCalculateSignature(mHashToSign, mHashAlgo);
+ protected void onNfcPerform() throws IOException {
- if (signedHash == null) {
- setResult(RESULT_CANCELED, mServiceIntent);
- finish();
- return;
- }
+ final byte[] nfcFingerprints = nfcGetFingerprints();
+ final String nfcUserId = nfcGetUserId();
+ final byte[] nfcAid = nfcGetAid();
- Log.d(Constants.TAG, "NfcActivity signedHash as hex: " + getHex(signedHash));
+ final long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(nfcFingerprints);
- // give data through for new service call
- // OpenPgpApi.EXTRA_NFC_SIGNED_HASH
- mServiceIntent.putExtra("nfc_signed_hash", signedHash);
- setResult(RESULT_OK, mServiceIntent);
+ try {
+ CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing(masterKeyId);
+ ring.getMasterKeyId();
+
+ Intent intent = new Intent(
+ BaseNfcActivity.this, ViewKeyActivity.class);
+ intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId));
+ intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, nfcAid);
+ intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, nfcUserId);
+ intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, nfcFingerprints);
+ startActivity(intent);
finish();
-
- } else if (ACTION_DECRYPT_SESSION_KEY.equals(mAction)) {
-
- // If we were supplied with a key id for checking, do so
- if (mKeyId != null) {
- // For decryption, we check the confidentiality key
- long keyId = nfcGetKeyId(mIsoDep, 1);
- // If it's wrong, just cancel
- if (keyId != mKeyId) {
- toast("NFC Tag has wrong encryption key id!");
- setResult(RESULT_CANCELED, mServiceIntent);
- finish();
- return;
- }
- }
-
- byte[] decryptedSessionKey = nfcDecryptSessionKey(mEncryptedSessionKey);
-
- // give data through for new service call
- // OpenPgpApi.EXTRA_NFC_DECRYPTED_SESSION_KEY
- mServiceIntent.putExtra("nfc_decrypted_session_key", decryptedSessionKey);
- setResult(RESULT_OK, mServiceIntent);
+ } catch (PgpKeyNotFoundException e) {
+ Intent intent = new Intent(
+ BaseNfcActivity.this, CreateKeyActivity.class);
+ intent.putExtra(CreateKeyActivity.EXTRA_NFC_AID, nfcAid);
+ intent.putExtra(CreateKeyActivity.EXTRA_NFC_USER_ID, nfcUserId);
+ intent.putExtra(CreateKeyActivity.EXTRA_NFC_FINGERPRINTS, nfcFingerprints);
+ startActivity(intent);
finish();
}
}
- /**
- * Gets the user ID
- *
- * @return the user id as "name <email>"
- * @throws java.io.IOException
- */
- public String getUserId() throws IOException {
- String info = "00CA006500";
- String data = "00CA005E00";
- return getName(card(info)) + " <" + (new String(Hex.decode(getDataField(card(data))))) + ">";
- }
-
/** Return the key id from application specific data stored on tag, or null
* if it doesn't exist.
*
* @param idx Index of the key to return the fingerprint from.
* @return The long key id of the requested key, or null if not found.
*/
- public static Long nfcGetKeyId(IsoDep isoDep, int idx) throws IOException {
- byte[] fp = nfcGetFingerprint(isoDep, idx);
+ public Long nfcGetKeyId(int idx) throws IOException {
+ byte[] fp = nfcGetFingerprint(idx);
if (fp == null) {
return null;
}
@@ -318,9 +263,9 @@ public class NfcActivity extends BaseActivity {
*
* @return The fingerprints of all subkeys in a contiguous byte array.
*/
- public static byte[] nfcGetFingerprints(IsoDep isoDep) throws IOException {
+ public byte[] nfcGetFingerprints() throws IOException {
String data = "00CA006E00";
- byte[] buf = isoDep.transceive(Hex.decode(data));
+ byte[] buf = mIsoDep.transceive(Hex.decode(data));
Iso7816TLV tlv = Iso7816TLV.readSingle(buf, true);
Log.d(Constants.TAG, "nfc tlv data:\n" + tlv.prettyPrint());
@@ -339,26 +284,39 @@ public class NfcActivity extends BaseActivity {
* @param idx Index of the key to return the fingerprint from.
* @return The fingerprint of the requested key, or null if not found.
*/
- public static byte[] nfcGetFingerprint(IsoDep isoDep, int idx) throws IOException {
- byte[] data = nfcGetFingerprints(isoDep);
+ public byte[] nfcGetFingerprint(int idx) throws IOException {
+ byte[] data = nfcGetFingerprints();
// return the master key fingerprint
ByteBuffer fpbuf = ByteBuffer.wrap(data);
byte[] fp = new byte[20];
- fpbuf.position(idx*20);
+ fpbuf.position(idx * 20);
fpbuf.get(fp, 0, 20);
return fp;
}
+ public byte[] nfcGetAid() throws IOException {
+
+ String info = "00CA004F00";
+ return mIsoDep.transceive(Hex.decode(info));
+
+ }
+
+ public String nfcGetUserId() throws IOException {
+
+ String info = "00CA006500";
+ return nfcGetHolderName(nfcCommunicate(info));
+ }
+
/**
* Calls to calculate the signature and returns the MPI value
*
* @param hash the hash for signing
* @return a big integer representing the MPI for the given hash
- * @throws java.io.IOException
*/
public byte[] nfcCalculateSignature(byte[] hash, int hashAlgo) throws IOException {
+ nfcVerifyPIN(0x81); // (Verify PW1 with mode 81 for signing)
// dsi, including Lc
String dsi;
@@ -367,7 +325,7 @@ public class NfcActivity extends BaseActivity {
switch (hashAlgo) {
case HashAlgorithmTags.SHA1:
if (hash.length != 20) {
- throw new RuntimeException("Bad hash length (" + hash.length + ", expected 10!");
+ throw new IOException("Bad hash length (" + hash.length + ", expected 10!");
}
dsi = "23" // Lc
+ "3021" // Tag/Length of Sequence, the 0x21 includes all following 33 bytes
@@ -378,45 +336,45 @@ public class NfcActivity extends BaseActivity {
break;
case HashAlgorithmTags.RIPEMD160:
if (hash.length != 20) {
- throw new RuntimeException("Bad hash length (" + hash.length + ", expected 20!");
+ throw new IOException("Bad hash length (" + hash.length + ", expected 20!");
}
dsi = "233021300906052B2403020105000414" + getHex(hash);
break;
case HashAlgorithmTags.SHA224:
if (hash.length != 28) {
- throw new RuntimeException("Bad hash length (" + hash.length + ", expected 28!");
+ throw new IOException("Bad hash length (" + hash.length + ", expected 28!");
}
dsi = "2F302D300D06096086480165030402040500041C" + getHex(hash);
break;
case HashAlgorithmTags.SHA256:
if (hash.length != 32) {
- throw new RuntimeException("Bad hash length (" + hash.length + ", expected 32!");
+ throw new IOException("Bad hash length (" + hash.length + ", expected 32!");
}
dsi = "333031300D060960864801650304020105000420" + getHex(hash);
break;
case HashAlgorithmTags.SHA384:
if (hash.length != 48) {
- throw new RuntimeException("Bad hash length (" + hash.length + ", expected 48!");
+ throw new IOException("Bad hash length (" + hash.length + ", expected 48!");
}
dsi = "433041300D060960864801650304020205000430" + getHex(hash);
break;
case HashAlgorithmTags.SHA512:
if (hash.length != 64) {
- throw new RuntimeException("Bad hash length (" + hash.length + ", expected 64!");
+ throw new IOException("Bad hash length (" + hash.length + ", expected 64!");
}
dsi = "533051300D060960864801650304020305000440" + getHex(hash);
break;
default:
- throw new RuntimeException("Not supported hash algo!");
+ throw new IOException("Not supported hash algo!");
}
// Command APDU for PERFORM SECURITY OPERATION: COMPUTE DIGITAL SIGNATURE (page 37)
String apdu =
- "002A9E9A" // CLA, INS, P1, P2
- + dsi // digital signature input
- + "00"; // Le
+ "002A9E9A" // CLA, INS, P1, P2
+ + dsi // digital signature input
+ + "00"; // Le
- String response = card(apdu);
+ String response = nfcCommunicate(apdu);
// split up response into signature and status
String status = response.substring(response.length()-4);
@@ -426,22 +384,20 @@ public class NfcActivity extends BaseActivity {
while (status.substring(0, 2).equals("61")) {
Log.d(Constants.TAG, "requesting more data, status " + status);
// Send GET RESPONSE command
- response = card("00C00000" + status.substring(2));
+ response = nfcCommunicate("00C00000" + status.substring(2));
status = response.substring(response.length()-4);
signature += response.substring(0, response.length()-4);
}
Log.d(Constants.TAG, "final response:" + status);
- if ( ! status.equals("9000")) {
- toast("Bad NFC response code: " + status);
- return null;
+ if ( ! "9000".equals(status)) {
+ throw new IOException("Bad NFC response code: " + status);
}
// Make sure the signature we received is actually the expected number of bytes long!
if (signature.length() != 256 && signature.length() != 512) {
- toast("Bad signature length! Expected 128 or 256 bytes, got " + signature.length() / 2);
- return null;
+ throw new IOException("Bad signature length! Expected 128 or 256 bytes, got " + signature.length() / 2);
}
return Hex.decode(signature);
@@ -452,9 +408,10 @@ public class NfcActivity extends BaseActivity {
*
* @param encryptedSessionKey the encoded session key
* @return the decoded session key
- * @throws java.io.IOException
*/
public byte[] nfcDecryptSessionKey(byte[] encryptedSessionKey) throws IOException {
+ nfcVerifyPIN(0x82); // (Verify PW1 with mode 82 for decryption)
+
String firstApdu = "102a8086fe";
String secondApdu = "002a808603";
String le = "00";
@@ -468,22 +425,48 @@ public class NfcActivity extends BaseActivity {
two[i] = encryptedSessionKey[i + one.length + 1];
}
- String first = card(firstApdu + getHex(one));
- String second = card(secondApdu + getHex(two) + le);
+ String first = nfcCommunicate(firstApdu + getHex(one));
+ String second = nfcCommunicate(secondApdu + getHex(two) + le);
- String decryptedSessionKey = getDataField(second);
+ String decryptedSessionKey = nfcGetDataField(second);
Log.d(Constants.TAG, "decryptedSessionKey: " + decryptedSessionKey);
return Hex.decode(decryptedSessionKey);
}
+ /** Verifies the user's PW1 with the appropriate mode.
+ *
+ * @param mode This is 0x81 for signing, 0x82 for everything else
+ */
+ public void nfcVerifyPIN(int mode) throws IOException {
+ if (mPin != null) {
+ byte[] pin = new String(mPin.getCharArray()).getBytes();
+ // SW1/2 0x9000 is the generic "ok" response, which we expect most of the time.
+ // See specification, page 51
+ String accepted = "9000";
+
+ // Command APDU for VERIFY command (page 32)
+ String login =
+ "00" // CLA
+ + "20" // INS
+ + "00" // P1
+ + String.format("%02x", mode) // P2
+ + String.format("%02x", pin.length) // Lc
+ + Hex.toHexString(pin);
+ if (!nfcCommunicate(login).equals(accepted)) { // login
+ handlePinError();
+ throw new IOException("Bad PIN!");
+ }
+ }
+ }
+
/**
* Prints a message to the screen
*
* @param text the text which should be contained within the toast
*/
- private void toast(String text) {
+ protected void toast(String text) {
Toast.makeText(this, text, Toast.LENGTH_LONG).show();
}
@@ -493,7 +476,7 @@ public class NfcActivity extends BaseActivity {
*/
public void enableNfcForegroundDispatch() {
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
- Intent nfcI = new Intent(this, NfcActivity.class)
+ Intent nfcI = new Intent(this, getClass())
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent nfcPendingIntent = PendingIntent.getActivity(this, 0, nfcI, PendingIntent.FLAG_CANCEL_CURRENT);
IntentFilter[] writeTagFilters = new IntentFilter[]{
@@ -518,13 +501,7 @@ public class NfcActivity extends BaseActivity {
Log.d(Constants.TAG, "NfcForegroundDispatch has been disabled!");
}
- /**
- * Gets the name of the user out of the raw card output regarding card holder related data
- *
- * @param name the raw card holder related data from the card
- * @return the name given in this data
- */
- public String getName(String name) {
+ public String nfcGetHolderName(String name) {
String slength;
int ilength;
name = name.substring(6);
@@ -535,34 +512,16 @@ public class NfcActivity extends BaseActivity {
return (name);
}
- /**
- * Reduces the raw data from the card by four characters
- *
- * @param output the raw data from the card
- * @return the data field of that data
- */
- private String getDataField(String output) {
+ private String nfcGetDataField(String output) {
return output.substring(0, output.length() - 4);
}
- /**
- * Communicates with the OpenPgpCard via the APDU
- *
- * @param hex the hexadecimal APDU
- * @return The answer from the card
- * @throws java.io.IOException throws an exception if something goes wrong
- */
- public String card(String hex) throws IOException {
- return getHex(mIsoDep.transceive(Hex.decode(hex)));
+ public String nfcCommunicate(String apdu) throws IOException {
+ return getHex(mIsoDep.transceive(Hex.decode(apdu)));
}
- /**
- * Converts a byte array into an hex string
- *
- * @param raw the byte array representation
- * @return the hexadecimal string representation
- */
public static String getHex(byte[] raw) {
return new String(Hex.encode(raw));
}
+
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java
new file mode 100644
index 000000000..232a39f86
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2015 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.ui.base;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Message;
+import android.support.v4.app.Fragment;
+
+import org.sufficientlysecure.keychain.operations.results.InputPendingResult;
+import org.sufficientlysecure.keychain.operations.results.OperationResult;
+import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
+import org.sufficientlysecure.keychain.ui.NfcOperationActivity;
+import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity;
+
+/**
+ * All fragments executing crypto operations need to extend this class.
+ */
+public abstract class CryptoOperationFragment extends Fragment {
+
+ public static final int REQUEST_CODE_PASSPHRASE = 0x00008001;
+ public static final int REQUEST_CODE_NFC = 0x00008002;
+
+ private void initiateInputActivity(RequiredInputParcel requiredInput) {
+
+ switch (requiredInput.mType) {
+ case NFC_DECRYPT:
+ case NFC_SIGN: {
+ Intent intent = new Intent(getActivity(), NfcOperationActivity.class);
+ intent.putExtra(NfcOperationActivity.EXTRA_REQUIRED_INPUT, requiredInput);
+ startActivityForResult(intent, REQUEST_CODE_NFC);
+ return;
+ }
+
+ case PASSPHRASE:
+ case PASSPHRASE_SYMMETRIC: {
+ Intent intent = new Intent(getActivity(), PassphraseDialogActivity.class);
+ intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT, requiredInput);
+ startActivityForResult(intent, REQUEST_CODE_PASSPHRASE);
+ return;
+ }
+ }
+
+ throw new RuntimeException("Unhandled pending result!");
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (resultCode == Activity.RESULT_CANCELED) {
+ onCryptoOperationCancelled();
+ return;
+ }
+
+ switch (requestCode) {
+ case REQUEST_CODE_PASSPHRASE: {
+ if (resultCode == Activity.RESULT_OK && data != null) {
+ CryptoInputParcel cryptoInput =
+ data.getParcelableExtra(PassphraseDialogActivity.RESULT_CRYPTO_INPUT);
+ cryptoOperation(cryptoInput);
+ return;
+ }
+ break;
+ }
+
+ case REQUEST_CODE_NFC: {
+ if (resultCode == Activity.RESULT_OK && data != null) {
+ CryptoInputParcel cryptoInput =
+ data.getParcelableExtra(NfcOperationActivity.RESULT_DATA);
+ cryptoOperation(cryptoInput);
+ return;
+ }
+ break;
+ }
+
+ default: {
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+ }
+
+ public boolean handlePendingMessage(Message message) {
+
+ if (message.arg1 == ServiceProgressHandler.MessageStatus.OKAY.ordinal()) {
+ Bundle data = message.getData();
+
+ OperationResult result = data.getParcelable(OperationResult.EXTRA_RESULT);
+ if (result == null || !(result instanceof InputPendingResult)) {
+ return false;
+ }
+
+ InputPendingResult pendingResult = (InputPendingResult) result;
+ if (pendingResult.isPending()) {
+ RequiredInputParcel requiredInput = pendingResult.getRequiredInputParcel();
+ initiateInputActivity(requiredInput);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ protected void cryptoOperation() {
+ cryptoOperation(new CryptoInputParcel());
+ }
+
+ protected abstract void cryptoOperation(CryptoInputParcel cryptoInput);
+
+ protected void onCryptoOperationCancelled() {
+ // Nothing to do here, in most cases
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java
index bd4e5577b..956171349 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java
@@ -44,11 +44,24 @@ public class DeleteFileDialogFragment extends DialogFragment {
/**
* Creates new instance of this delete file dialog fragment
*/
- public static DeleteFileDialogFragment newInstance(Uri... deleteUris) {
+ public static DeleteFileDialogFragment newInstance(ArrayList<Uri> deleteUris) {
DeleteFileDialogFragment frag = new DeleteFileDialogFragment();
Bundle args = new Bundle();
- args.putParcelableArray(ARG_DELETE_URIS, deleteUris);
+ args.putParcelableArrayList(ARG_DELETE_URIS, deleteUris);
+
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ public static DeleteFileDialogFragment newInstance(Uri deleteUri) {
+ DeleteFileDialogFragment frag = new DeleteFileDialogFragment();
+ Bundle args = new Bundle();
+
+ ArrayList<Uri> list = new ArrayList<>();
+ list.add(deleteUri);
+ args.putParcelableArrayList(ARG_DELETE_URIS, list);
frag.setArguments(args);
@@ -62,7 +75,7 @@ public class DeleteFileDialogFragment extends DialogFragment {
public Dialog onCreateDialog(Bundle savedInstanceState) {
final FragmentActivity activity = getActivity();
- final Uri[] deleteUris = (Uri[]) getArguments().getParcelableArray(ARG_DELETE_URIS);
+ final ArrayList<Uri> deleteUris = getArguments().getParcelableArrayList(ARG_DELETE_URIS);
final StringBuilder deleteFileNames = new StringBuilder();
//Retrieving file names after deletion gives unexpected results
@@ -127,7 +140,7 @@ public class DeleteFileDialogFragment extends DialogFragment {
// NOTE: Use Toasts, not Snackbars. When sharing to another application snackbars
// would not show up!
Toast.makeText(getActivity(), getActivity().getString(R.string.file_delete_successful,
- deleteUris.length - failedFileNameList.size(), deleteUris.length, failedFileNames.toString()),
+ deleteUris.size() - failedFileNameList.size(), deleteUris.size(), failedFileNames.toString()),
Toast.LENGTH_LONG).show();
if (onDeletedListener != null) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java
index 947c316e0..4eb253825 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java
@@ -50,7 +50,6 @@ import org.sufficientlysecure.keychain.util.Passphrase;
public class SetPassphraseDialogFragment extends DialogFragment implements OnEditorActionListener {
private static final String ARG_MESSENGER = "messenger";
private static final String ARG_TITLE = "title";
- private static final String ARG_OLD_PASSPHRASE = "old_passphrase";
public static final int MESSAGE_OKAY = 1;
@@ -68,12 +67,11 @@ public class SetPassphraseDialogFragment extends DialogFragment implements OnEdi
* @param messenger to communicate back after setting the passphrase
* @return
*/
- public static SetPassphraseDialogFragment newInstance(Messenger messenger, Passphrase oldPassphrase, int title) {
+ public static SetPassphraseDialogFragment newInstance(Messenger messenger, int title) {
SetPassphraseDialogFragment frag = new SetPassphraseDialogFragment();
Bundle args = new Bundle();
args.putInt(ARG_TITLE, title);
args.putParcelable(ARG_MESSENGER, messenger);
- args.putParcelable(ARG_OLD_PASSPHRASE, oldPassphrase);
frag.setArguments(args);
@@ -89,7 +87,6 @@ public class SetPassphraseDialogFragment extends DialogFragment implements OnEdi
int title = getArguments().getInt(ARG_TITLE);
mMessenger = getArguments().getParcelable(ARG_MESSENGER);
- Passphrase oldPassphrase = getArguments().getParcelable(ARG_OLD_PASSPHRASE);
CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity);
@@ -103,13 +100,6 @@ public class SetPassphraseDialogFragment extends DialogFragment implements OnEdi
mPassphraseAgainEditText = (EditText) view.findViewById(R.id.passphrase_passphrase_again);
mNoPassphraseCheckBox = (CheckBox) view.findViewById(R.id.passphrase_no_passphrase);
-
- if (oldPassphrase.isEmpty()) {
- mNoPassphraseCheckBox.setChecked(true);
- mPassphraseEditText.setEnabled(false);
- mPassphraseAgainEditText.setEnabled(false);
- }
-
mNoPassphraseCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateFinalFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateFinalFragment.java
index ac7dff9bd..0767aabd2 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateFinalFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateFinalFragment.java
@@ -27,6 +27,7 @@ import org.sufficientlysecure.keychain.pgp.linked.LinkedIdentity;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment.ServiceType;
import org.sufficientlysecure.keychain.ui.util.Notify;
@@ -186,7 +187,7 @@ public abstract class LinkedIdCreateFinalFragment extends Fragment {
}
- private void certifyLinkedIdentity (Passphrase passphrase) {
+ private void certifyLinkedIdentity (CryptoInputParcel cryptoInput) {
ServiceProgressHandler saveHandler = new ServiceProgressHandler(
getActivity(),
getString(R.string.progress_saving),
@@ -235,7 +236,7 @@ public abstract class LinkedIdCreateFinalFragment extends Fragment {
// fill values for this action
Bundle data = new Bundle();
- data.putParcelable(KeychainIntentService.EDIT_KEYRING_PASSPHRASE, passphrase);
+ data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
data.putParcelable(KeychainIntentService.EDIT_KEYRING_PARCEL, skp);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
@@ -257,9 +258,9 @@ public abstract class LinkedIdCreateFinalFragment extends Fragment {
switch (requestCode) {
case REQUEST_CODE_PASSPHRASE:
if (resultCode == Activity.RESULT_OK && data != null) {
- Passphrase passphrase =
- data.getParcelableExtra(PassphraseDialogActivity.MESSAGE_DATA_PASSPHRASE);
- certifyLinkedIdentity(passphrase);
+ CryptoInputParcel cryptoInput =
+ data.getParcelableExtra(PassphraseDialogActivity.RESULT_CRYPTO_INPUT);
+ certifyLinkedIdentity(cryptoInput);
}
break;
default:
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdViewFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdViewFragment.java
index 0ef63f724..1944146dd 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdViewFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdViewFragment.java
@@ -1,7 +1,7 @@
package org.sufficientlysecure.keychain.ui.linked;
import java.io.IOException;
-import java.util.Arrays;
+import java.util.Collections;
import android.animation.ObjectAnimator;
import android.app.Activity;
@@ -47,6 +47,7 @@ import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyActio
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity;
import org.sufficientlysecure.keychain.ui.adapter.LinkedIdsAdapter;
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;
@@ -86,6 +87,7 @@ public class LinkedIdViewFragment extends Fragment implements
private ViewHolder mViewHolder;
private int mLidRank;
private OnIdentityLoadedListener mIdLoadedListener;
+ private long mCertifyKeyId;
public static LinkedIdViewFragment newInstance(Uri dataUri, int rank,
boolean isSecret, byte[] fingerprint) throws IOException {
@@ -183,7 +185,7 @@ public class LinkedIdViewFragment extends Fragment implements
}
public interface OnIdentityLoadedListener {
- public void onIdentityLoaded();
+ void onIdentityLoaded();
}
public void setOnIdentityLoadedListener(OnIdentityLoadedListener listener) {
@@ -500,8 +502,8 @@ public class LinkedIdViewFragment extends Fragment implements
}
// get the user's passphrase for this key (if required)
- long certifyKeyId = mViewHolder.vKeySpinner.getSelectedItemId();
- if (certifyKeyId == key.none || certifyKeyId == key.symmetric) {
+ mCertifyKeyId = mViewHolder.vKeySpinner.getSelectedItemId();
+ if (mCertifyKeyId == key.none || mCertifyKeyId == key.symmetric) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
SubtleAttentionSeeker.tint(mViewHolder.vKeySpinnerContainer, 600).start();
} else {
@@ -513,7 +515,7 @@ public class LinkedIdViewFragment extends Fragment implements
Passphrase passphrase;
try {
passphrase = PassphraseCacheService.getCachedPassphrase(
- getActivity(), certifyKeyId, certifyKeyId);
+ getActivity(), mCertifyKeyId, mCertifyKeyId);
} catch (PassphraseCacheService.KeyNotFoundException e) {
Log.e(Constants.TAG, "Key not found!", e);
getActivity().finish();
@@ -521,11 +523,11 @@ public class LinkedIdViewFragment extends Fragment implements
}
if (passphrase == null) {
Intent intent = new Intent(getActivity(), PassphraseDialogActivity.class);
- intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, certifyKeyId);
+ intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, mCertifyKeyId);
startActivityForResult(intent, REQUEST_CODE_PASSPHRASE);
// bail out; need to wait until the user has entered the passphrase before trying again
} else {
- certifyResource(certifyKeyId, "");
+ certifyResource(new CryptoInputParcel());
}
}
@@ -534,13 +536,9 @@ public class LinkedIdViewFragment extends Fragment implements
switch (requestCode) {
case REQUEST_CODE_PASSPHRASE: {
if (resultCode == Activity.RESULT_OK && data != null) {
- String passphrase = data.getStringExtra(
- PassphraseDialogActivity.MESSAGE_DATA_PASSPHRASE);
- long certifyKeyId = data.getLongExtra(PassphraseDialogActivity.EXTRA_KEY_ID, 0L);
- if (certifyKeyId == 0L) {
- throw new AssertionError("key id must not be 0");
- }
- certifyResource(certifyKeyId, passphrase);
+ CryptoInputParcel cryptoInput = data.getParcelableExtra(
+ PassphraseDialogActivity.RESULT_CRYPTO_INPUT);
+ certifyResource(cryptoInput);
}
return;
}
@@ -551,7 +549,7 @@ public class LinkedIdViewFragment extends Fragment implements
}
}
- private void certifyResource(long certifyKeyId, String passphrase) {
+ private void certifyResource(CryptoInputParcel cryptoInput) {
if (mIsSecret) {
return;
@@ -564,13 +562,15 @@ public class LinkedIdViewFragment extends Fragment implements
long masterKeyId = KeyFormattingUtils.convertFingerprintToKeyId(mFingerprint);
CertifyAction action = new CertifyAction(masterKeyId, null,
- Arrays.asList(mLinkedId.toUserAttribute()));
+ Collections.singletonList(mLinkedId.toUserAttribute()));
// fill values for this action
- CertifyActionsParcel parcel = new CertifyActionsParcel(certifyKeyId);
- parcel.mCertifyActions.addAll(Arrays.asList(action));
-
+ CertifyActionsParcel parcel = new CertifyActionsParcel(mCertifyKeyId);
+ parcel.mCertifyActions.addAll(Collections.singletonList(action));
data.putParcelable(KeychainIntentService.CERTIFY_PARCEL, parcel);
+
+ data.putParcelable(KeychainIntentService.EXTRA_CRYPTO_INPUT, cryptoInput);
+
/* if (mUploadKeyCheckbox.isChecked()) {
String keyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver();
data.putString(KeychainIntentService.UPLOAD_KEY_SERVER, keyserver);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java
index 57a71b084..aaf127614 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java
@@ -216,7 +216,15 @@ public class KeyFormattingUtils {
* @return
*/
public static String convertFingerprintToHex(byte[] fingerprint) {
- return Hex.toHexString(fingerprint).toLowerCase(Locale.ENGLISH);
+ return Hex.toHexString(fingerprint, 0, 20).toLowerCase(Locale.ENGLISH);
+ }
+
+ public static long getKeyIdFromFingerprint(byte[] fingerprint) {
+ ByteBuffer buf = ByteBuffer.wrap(fingerprint);
+ // skip first 12 bytes of the fingerprint
+ buf.position(12);
+ // the last eight bytes are the key id (big endian, which is default order in ByteBuffer)
+ return buf.getLong();
}
/**
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/AlgorithmNames.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/AlgorithmNames.java
deleted file mode 100644
index c1955f75b..000000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/AlgorithmNames.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2013-2014 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.util;
-
-import android.annotation.SuppressLint;
-import android.app.Activity;
-
-import org.spongycastle.bcpg.CompressionAlgorithmTags;
-import org.spongycastle.bcpg.HashAlgorithmTags;
-import org.spongycastle.openpgp.PGPEncryptedData;
-import org.sufficientlysecure.keychain.R;
-
-import java.util.HashMap;
-
-@SuppressLint("UseSparseArrays")
-public class AlgorithmNames {
- Activity mActivity;
-
- HashMap<Integer, String> mEncryptionNames = new HashMap<>();
- HashMap<Integer, String> mHashNames = new HashMap<>();
- HashMap<Integer, String> mCompressionNames = new HashMap<>();
-
- public AlgorithmNames(Activity context) {
- super();
- this.mActivity = context;
-
- mEncryptionNames.put(PGPEncryptedData.AES_128, "AES-128");
- mEncryptionNames.put(PGPEncryptedData.AES_192, "AES-192");
- mEncryptionNames.put(PGPEncryptedData.AES_256, "AES-256");
- mEncryptionNames.put(PGPEncryptedData.BLOWFISH, "Blowfish");
- mEncryptionNames.put(PGPEncryptedData.TWOFISH, "Twofish");
- mEncryptionNames.put(PGPEncryptedData.CAST5, "CAST5");
- mEncryptionNames.put(PGPEncryptedData.DES, "DES");
- mEncryptionNames.put(PGPEncryptedData.TRIPLE_DES, "Triple DES");
- mEncryptionNames.put(PGPEncryptedData.IDEA, "IDEA");
-
- mHashNames.put(HashAlgorithmTags.RIPEMD160, "RIPEMD-160");
- mHashNames.put(HashAlgorithmTags.SHA1, "SHA-1");
- mHashNames.put(HashAlgorithmTags.SHA224, "SHA-224");
- mHashNames.put(HashAlgorithmTags.SHA256, "SHA-256");
- mHashNames.put(HashAlgorithmTags.SHA384, "SHA-384");
- mHashNames.put(HashAlgorithmTags.SHA512, "SHA-512");
-
- mCompressionNames.put(CompressionAlgorithmTags.UNCOMPRESSED, mActivity.getString(R.string.choice_none)
- + " (" + mActivity.getString(R.string.compression_fast) + ")");
- mCompressionNames.put(CompressionAlgorithmTags.ZIP,
- "ZIP (" + mActivity.getString(R.string.compression_fast) + ")");
- mCompressionNames.put(CompressionAlgorithmTags.ZLIB,
- "ZLIB (" + mActivity.getString(R.string.compression_fast) + ")");
- mCompressionNames.put(CompressionAlgorithmTags.BZIP2,
- "BZIP2 (" + mActivity.getString(R.string.compression_very_slow) + ")");
- }
-
- public HashMap<Integer, String> getEncryptionNames() {
- return mEncryptionNames;
- }
-
- public void setEncryptionNames(HashMap<Integer, String> encryptionNames) {
- this.mEncryptionNames = encryptionNames;
- }
-
- public HashMap<Integer, String> getHashNames() {
- return mHashNames;
- }
-
- public void setHashNames(HashMap<Integer, String> hashNames) {
- this.mHashNames = hashNames;
- }
-
- public HashMap<Integer, String> getCompressionNames() {
- return mCompressionNames;
- }
-
- public void setCompressionNames(HashMap<Integer, String> compressionNames) {
- this.mCompressionNames = compressionNames;
- }
-
-}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java
index c782d2507..609288bf1 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java
@@ -220,15 +220,14 @@ public class ContactHelper {
*/
public static long getMainProfileContactId(ContentResolver resolver) {
Cursor profileCursor = resolver.query(ContactsContract.Profile.CONTENT_URI,
- new String[]{ ContactsContract.Profile._ID}, null, null, null);
+ new String[]{ContactsContract.Profile._ID}, null, null, null);
- if(profileCursor != null && profileCursor.getCount() != 0 && profileCursor.moveToNext()) {
+ if (profileCursor != null && profileCursor.getCount() != 0 && profileCursor.moveToNext()) {
long contactId = profileCursor.getLong(0);
profileCursor.close();
return contactId;
- }
- else {
- if(profileCursor != null) {
+ } else {
+ if (profileCursor != null) {
profileCursor.close();
}
return -1;
@@ -330,7 +329,9 @@ public class ContactHelper {
ContactsContract.RawContacts.SOURCE_ID + "=? AND " +
ContactsContract.RawContacts.DELETED + "=?",
new String[]{//"0" for "not deleted"
- Constants.ACCOUNT_TYPE, Long.toString(masterKeyId), "0"
+ Constants.ACCOUNT_TYPE,
+ Long.toString(masterKeyId),
+ "0"
}, null);
if (raw != null) {
if (raw.moveToNext()) {
@@ -385,23 +386,37 @@ public class ContactHelper {
return null;
}
try {
- long rawContactId = findRawContactId(contentResolver, masterKeyId);
- if (rawContactId == -1) {
- return null;
- }
- Uri rawContactUri = ContentUris.withAppendedId(ContactsContract.RawContacts.CONTENT_URI, rawContactId);
- Uri contactUri = ContactsContract.RawContacts.getContactLookupUri(contentResolver, rawContactUri);
- InputStream photoInputStream =
- ContactsContract.Contacts.openContactPhotoInputStream(contentResolver, contactUri, highRes);
- if (photoInputStream == null) {
- return null;
- }
- return BitmapFactory.decodeStream(photoInputStream);
+ long contactId = findContactId(contentResolver, masterKeyId);
+ return loadPhotoByContactId(contentResolver, contactId, highRes);
+
} catch (Throwable ignored) {
return null;
}
}
+ public static Bitmap loadPhotoByContactId(ContentResolver contentResolver, long contactId,
+ boolean highRes) {
+ if (contactId == -1) {
+ return null;
+ }
+ Uri contactUri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contactId);
+ // older android versions (tested on API level 15) fail on lookupuris being passed to
+ // openContactPhotoInputStream
+ // http://stackoverflow.com/a/21214524/3000919
+ // Uri lookupUri = ContactsContract.Contacts.getLookupUri(contentResolver, contactUri);
+ // Also, we don't need a permanent shortcut to the contact since we load it afresh each time
+
+ InputStream photoInputStream = ContactsContract.Contacts.openContactPhotoInputStream(
+ contentResolver,
+ contactUri,
+ highRes);
+
+ if (photoInputStream == null) {
+ return null;
+ }
+ return BitmapFactory.decodeStream(photoInputStream);
+ }
+
public static final String[] KEYS_TO_CONTACT_PROJECTION = new String[]{
KeychainContract.KeyRings.MASTER_KEY_ID,
KeychainContract.KeyRings.USER_ID,
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/DatabaseUtil.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/DatabaseUtil.java
index c18e5cabd..93b6a5697 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/DatabaseUtil.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/DatabaseUtil.java
@@ -1,3 +1,20 @@
+/*
+ * Copyright (C) 2012-2014 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.util;
import android.text.TextUtils;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FabContainer.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FabContainer.java
index 116b98d1e..ffc0484f7 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FabContainer.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FabContainer.java
@@ -1,3 +1,20 @@
+/*
+ * Copyright (C) 2012-2014 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.util;
public interface FabContainer {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeyUpdateHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeyUpdateHelper.java
index 943b913d7..3bbd86d6a 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeyUpdateHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/KeyUpdateHelper.java
@@ -57,7 +57,7 @@ public class KeyUpdateHelper {
Bundle importData = new Bundle();
importData.putParcelableArrayList(KeychainIntentService.DOWNLOAD_KEY_LIST,
new ArrayList<ImportKeysListEntry>(keys));
- importIntent.putExtra(KeychainIntentService.EXTRA_DATA, importData);
+ importIntent.putExtra(KeychainIntentService.EXTRA_SERVICE_INTENT, importData);
importIntent.putExtra(KeychainIntentService.EXTRA_MESSENGER, new Messenger(mHandler));
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/NfcHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/NfcHelper.java
index e4e4e4d05..2b47fd623 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/NfcHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/NfcHelper.java
@@ -191,9 +191,6 @@ public class NfcHelper {
mNfcAdapter.invokeBeam(mActivity);
}
- /**
- * A static subclass of {@link Handler} with a {@link WeakReference} to an {@link Activity} to avoid memory leaks.
- */
private static class NfcHandler extends Handler {
private final WeakReference<Activity> mActivityReference;
@@ -203,12 +200,10 @@ public class NfcHelper {
@Override
public void handleMessage(Message msg) {
- Activity activity = mActivityReference.get();
-
- if (activity != null) {
+ if (mActivityReference.get() != null) {
switch (msg.what) {
case NFC_SENT:
- Notify.create(activity, R.string.nfc_successful, Notify.Style.OK).show();
+ Notify.create(mActivityReference.get(), R.string.nfc_successful, Notify.Style.OK).show();
break;
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableCache.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableCache.java
new file mode 100644
index 000000000..75bdee00a
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableCache.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2015 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.util;
+
+import android.os.Parcel;
+
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * If Parcelables are above 1 MB, Android OS fails to send them via the Binder IPC:
+ * JavaBinder E !!! FAILED BINDER TRANSACTION !!!
+ * To overcome this issue this class allows to cache Parcelables, mapped by unique UUIDs,
+ * which are written to the parcel instead of the whole Parcelable.
+ */
+public class ParcelableCache<E> {
+
+ private static final UUID NULL_UUID = new UUID(0, 0);
+
+ /**
+ * A HashMap of UUID:Object
+ * This is used such that when we become parceled, we are
+ * well below the 1 MB boundary that is specified.
+ */
+ private ConcurrentHashMap<UUID, E> objectCache = new ConcurrentHashMap<>();
+
+ /**
+ * Dehydrate a Parcelable (such that it is available after deparcelization)
+ * Returns the NULL uuid (0) if you hand it null.
+ *
+ * @param parcelable A Parcelable to dehydrate
+ * @return a UUID, the ticket for your dehydrated Parcelable
+ */
+ private UUID dehydrateParcelable(E parcelable) {
+ if (parcelable == null) {
+ return NULL_UUID;
+ } else {
+ UUID uuid = UUID.randomUUID();
+ objectCache.put(uuid, parcelable);
+ return uuid;
+ }
+ }
+
+ /**
+ * Rehydrate a Parcelable after going through parcelization,
+ * invalidating its place in the dehydration pool.
+ * This is used such that when parcelized, the Parcelable is no larger than 1 MB.
+ *
+ * @param uuid A UUID ticket that identifies the log in question.
+ * @return An OperationLog.
+ */
+ private E rehydrateParcelable(UUID uuid) {
+ // UUID.equals isn't well documented; we use compareTo instead.
+ if (NULL_UUID.compareTo(uuid) == 0) {
+ return null;
+ } else {
+ E parcelable = objectCache.get(uuid);
+ objectCache.remove(uuid);
+ return parcelable;
+ }
+ }
+
+ public E readFromParcelAndGetFromCache(Parcel source) {
+ long mostSig = source.readLong();
+ long leastSig = source.readLong();
+ UUID mTicket = new UUID(mostSig, leastSig);
+ // fetch the dehydrated parcelable out of storage (this removes it from the dehydration pool)
+ return rehydrateParcelable(mTicket);
+ }
+
+ public void cacheAndWriteToParcel(E parcelable, Parcel dest) {
+ // Get a ticket for our parcelable.
+ UUID mTicket = dehydrateParcelable(parcelable);
+ // And write out the UUID most and least significant bits.
+ dest.writeLong(mTicket.getMostSignificantBits());
+ dest.writeLong(mTicket.getLeastSignificantBits());
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java
index 44c1e6b6c..8a7638054 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java
@@ -109,21 +109,21 @@ public class Preferences {
return mSharedPreferences.getBoolean(Constants.Pref.FIRST_TIME, true);
}
- public boolean useDefaultYubikeyPin() {
+ public boolean useDefaultYubiKeyPin() {
return mSharedPreferences.getBoolean(Pref.USE_DEFAULT_YUBIKEY_PIN, false);
}
- public void setUseDefaultYubikeyPin(boolean useDefaultYubikeyPin) {
+ public void setUseDefaultYubiKeyPin(boolean useDefaultYubikeyPin) {
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putBoolean(Pref.USE_DEFAULT_YUBIKEY_PIN, useDefaultYubikeyPin);
editor.commit();
}
- public boolean useNumKeypadForYubikeyPin() {
+ public boolean useNumKeypadForYubiKeyPin() {
return mSharedPreferences.getBoolean(Pref.USE_NUMKEYPAD_FOR_YUBIKEY_PIN, true);
}
- public void setUseNumKeypadForYubikeyPin(boolean useNumKeypadForYubikeyPin) {
+ public void setUseNumKeypadForYubiKeyPin(boolean useNumKeypadForYubikeyPin) {
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putBoolean(Pref.USE_NUMKEYPAD_FOR_YUBIKEY_PIN, useNumKeypadForYubikeyPin);
editor.commit();
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ProgressFixedScaler.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ProgressFixedScaler.java
index 4bb4ca5de..861298f56 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ProgressFixedScaler.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ProgressFixedScaler.java
@@ -1,3 +1,20 @@
+/*
+ * Copyright (C) 2014 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.util;
import org.sufficientlysecure.keychain.pgp.Progressable;