aboutsummaryrefslogtreecommitdiffstats
path: root/OpenKeychain/src/main/java/org
diff options
context:
space:
mode:
Diffstat (limited to 'OpenKeychain/src/main/java/org')
-rw-r--r--OpenKeychain/src/main/java/org/spongycastle/openpgp/jcajce/JcaSkipMarkerPGPObjectFactory.java32
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java36
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java10
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/experimental/BitInputStream.java130
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/experimental/SentenceConfirm.java210
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/experimental/WordConfirm.java (renamed from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ExperimentalWordConfirm.java)7
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/CloudSearch.java17
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java113
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java31
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java10
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedTokenResource.java5
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BackupOperation.java312
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java3
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BenchmarkOperation.java154
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java39
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java42
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ExportOperation.java385
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java139
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java62
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java36
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/RevokeOperation.java8
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java25
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/UploadOperation.java197
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/BenchmarkResult.java50
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/EditKeyResult.java5
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ExportResult.java21
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java7
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java57
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/PgpSignEncryptResult.java1
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/SignEncryptResult.java4
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/UploadResult.java74
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java7
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java8
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java5
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java14
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java648
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java61
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java4
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSecurityConstants.java4
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java12
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java24
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignatureChecker.java344
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java47
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java35
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java47
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java3
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryFileProvider.java (renamed from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java)105
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java244
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/BackupKeyringParcel.java78
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/BenchmarkInputParcel.java54
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ExportKeyringParcel.java111
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java12
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java122
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java8
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/UploadKeyringParcel.java79
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java1
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java14
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupActivity.java77
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java543
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupRestoreFragment.java (renamed from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupFragment.java)99
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintFragment.java43
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java37
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java22
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyImportResetFragment.java (renamed from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyImportFragment.java)125
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinFragment.java121
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinRepeatFragment.java152
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java6
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java411
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java8
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java20
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptFragment.java (renamed from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java)7
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java47
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java75
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java44
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java250
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java9
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java42
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java7
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java115
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java13
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java50
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java72
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java77
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeCaptureActivity.java125
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java84
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java22
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java36
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java86
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java3
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java127
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeybaseFragment.java35
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java51
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySelectableAdapter.java9
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/LinkedIdsAdapter.java9
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java186
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java10
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java80
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java38
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java234
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareLogDialogFragment.java79
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep2Fragment.java8
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java45
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Notify.java7
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java57
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java26
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java16
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java124
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java198
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelperLollipop.java82
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FilterCursorWrapper.java17
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/NfcHelper.java6
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java76
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableProxy.java9
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java19
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ShareHelper.java104
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java82
117 files changed, 5526 insertions, 3351 deletions
diff --git a/OpenKeychain/src/main/java/org/spongycastle/openpgp/jcajce/JcaSkipMarkerPGPObjectFactory.java b/OpenKeychain/src/main/java/org/spongycastle/openpgp/jcajce/JcaSkipMarkerPGPObjectFactory.java
new file mode 100644
index 000000000..f1cf9791a
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/spongycastle/openpgp/jcajce/JcaSkipMarkerPGPObjectFactory.java
@@ -0,0 +1,32 @@
+package org.spongycastle.openpgp.jcajce;
+
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.spongycastle.openpgp.PGPMarker;
+
+/** This class wraps the regular PGPObjectFactory, changing its behavior to
+ * ignore all PGPMarker packets it encounters while reading. These packets
+ * carry no semantics of their own, and should be ignored according to
+ * RFC 4880.
+ *
+ * @see https://tools.ietf.org/html/rfc4880#section-5.8
+ * @see org.spongycastle.openpgp.PGPMarker
+ *
+ */
+public class JcaSkipMarkerPGPObjectFactory extends JcaPGPObjectFactory {
+
+ public JcaSkipMarkerPGPObjectFactory(InputStream in) {
+ super(in);
+ }
+
+ @Override
+ public Object nextObject() throws IOException {
+ Object o = super.nextObject();
+ while (o instanceof PGPMarker) {
+ o = super.nextObject();
+ }
+ return o;
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java
index 6a9656b28..69f1862ce 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java
@@ -28,6 +28,7 @@ public final class Constants {
public static final boolean DEBUG = BuildConfig.DEBUG;
public static final boolean DEBUG_LOG_DB_QUERIES = false;
+ public static final boolean DEBUG_EXPLAIN_QUERIES = false;
public static final boolean DEBUG_SYNC_REMOVE_CONTACTS = false;
public static final boolean DEBUG_KEYSERVER_SYNC = false;
@@ -40,22 +41,31 @@ public final class Constants {
public static final String CUSTOM_CONTACT_DATA_MIME_TYPE = "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.key";
public static final String PROVIDER_AUTHORITY = BuildConfig.PROVIDER_CONTENT_AUTHORITY;
- public static final String TEMPSTORAGE_AUTHORITY = BuildConfig.APPLICATION_ID + ".tempstorage";
+ public static final String TEMP_FILE_PROVIDER_AUTHORITY = BuildConfig.APPLICATION_ID + ".tempstorage";
public static final String CLIPBOARD_LABEL = "Keychain";
- // as defined in http://tools.ietf.org/html/rfc3156, section 7
- public static final String NFC_MIME = "application/pgp-keys";
-
// as defined in http://tools.ietf.org/html/rfc3156
- // we don't use application/pgp-encrypted as it only holds the version number
- public static final String ENCRYPTED_FILES_MIME = "application/octet-stream";
- public static final String ENCRYPTED_TEXT_MIME = "text/plain";
-
- public static final String FILE_EXTENSION_PGP_MAIN = ".gpg";
- public static final String FILE_EXTENSION_PGP_ALTERNATE = ".pgp";
+ public static final String MIME_TYPE_KEYS = "application/pgp-keys";
+ // NOTE: don't use application/pgp-encrypted It only holds the version number!
+ public static final String MIME_TYPE_ENCRYPTED = "application/octet-stream";
+ // NOTE: Non-standard alternative, better use this, because application/octet-stream is too unspecific!
+ // also see https://tools.ietf.org/html/draft-bray-pgp-message-00
+ public static final String MIME_TYPE_ENCRYPTED_ALTERNATE = "application/pgp-message";
+ public static final String MIME_TYPE_TEXT = "text/plain";
+
+ public static final String FILE_EXTENSION_PGP_MAIN = ".pgp";
+ public static final String FILE_EXTENSION_PGP_ALTERNATE = ".gpg";
public static final String FILE_EXTENSION_ASC = ".asc";
+ public static final String FILE_BACKUP_PREFIX = "backup_";
+ public static final String FILE_EXTENSION_BACKUP_SECRET = ".sec.asc";
+ public static final String FILE_EXTENSION_BACKUP_PUBLIC = ".pub.asc";
+ public static final String FILE_ENCRYPTED_BACKUP_PREFIX = "backup_";
+ // actually it is ASCII Armor, so .asc would be more accurate, but Android displays a nice icon for .pgp files!
+ public static final String FILE_EXTENSION_ENCRYPTED_BACKUP_SECRET = ".sec.pgp";
+ public static final String FILE_EXTENSION_ENCRYPTED_BACKUP_PUBLIC = ".pub.pgp";
+
// used by QR Codes (Guardian Project, Monkeysphere compatiblity)
public static final String FINGERPRINT_SCHEME = "openpgp4fpr";
@@ -70,11 +80,13 @@ public final class Constants {
public static final int TEMPFILE_TTL = 24 * 60 * 60 * 1000; // 1 day
+ // the maximal length of plaintext to read in encrypt/decrypt text activities
+ public static final int TEXT_LENGTH_LIMIT = 1024 * 50;
+
public static final String SAFESLINGER_SERVER = "safeslinger-openpgp.appspot.com";
public static final class Path {
public static final File APP_DIR = new File(Environment.getExternalStorageDirectory(), "OpenKeychain");
- public static final File APP_DIR_FILE = new File(APP_DIR, "export.asc");
}
public static final class Notification {
@@ -92,7 +104,6 @@ public final class Constants {
public static final String CACHED_CONSOLIDATE = "cachedConsolidate";
public static final String SEARCH_KEYSERVER = "search_keyserver_pref";
public static final String SEARCH_KEYBASE = "search_keybase_pref";
- public static final String USE_DEFAULT_YUBIKEY_PIN = "useDefaultYubikeyPin";
public static final String USE_NUMKEYPAD_FOR_YUBIKEY_PIN = "useNumKeypadForYubikeyPin";
public static final String ENCRYPT_FILENAMES = "encryptFilenames";
public static final String FILE_USE_COMPRESSION = "useFileCompression";
@@ -142,6 +153,7 @@ public final class Constants {
public static final class key {
public static final int none = 0;
public static final int symmetric = -1;
+ public static final int backup_code = -2;
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java
index 311ef2d3b..5d97dac8a 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/KeychainApplication.java
@@ -33,7 +33,7 @@ import android.widget.Toast;
import org.spongycastle.jce.provider.BouncyCastleProvider;
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
-import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
+import org.sufficientlysecure.keychain.provider.TemporaryFileProvider;
import org.sufficientlysecure.keychain.service.KeyserverSyncAdapterService;
import org.sufficientlysecure.keychain.ui.ConsolidateDialogActivity;
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
@@ -91,16 +91,18 @@ public class KeychainApplication extends Application {
}
brandGlowEffect(getApplicationContext(),
- FormattingUtils.getColorFromAttr(getApplicationContext(), R.attr.colorPrimary));
+ FormattingUtils.getColorFromAttr(getApplicationContext(), R.attr.colorPrimary));
setupAccountAsNeeded(this);
// Update keyserver list as needed
Preferences.getPreferences(this).upgradePreferences(this);
- TlsHelper.addStaticCA("pool.sks-keyservers.net", getAssets(), "sks-keyservers.netCA.cer");
+ TlsHelper.addPinnedCertificate("hkps.pool.sks-keyservers.net", getAssets(), "hkps.pool.sks-keyservers.net.CA.cer");
+ TlsHelper.addPinnedCertificate("pgp.mit.edu", getAssets(), "pgp.mit.edu.cer");
+ TlsHelper.addPinnedCertificate("api.keybase.io", getAssets(), "api.keybase.io.CA.cer");
- TemporaryStorageProvider.cleanUp(this);
+ TemporaryFileProvider.cleanUp(this);
if (!checkConsolidateRecovery()) {
// force DB upgrade, https://github.com/open-keychain/open-keychain/issues/1334
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/experimental/BitInputStream.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/experimental/BitInputStream.java
new file mode 100644
index 000000000..111e0b366
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/experimental/BitInputStream.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) Andreas Jakl
+ *
+ * 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.experimental;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * The BitInputStream allows reading individual bits from a
+ * general Java InputStream.
+ * Like the various Stream-classes from Java, the BitInputStream
+ * has to be created based on another Input stream. It provides
+ * a function to read the next bit from the stream, as well as to read multiple
+ * bits at once and write the resulting data into an integer value.
+ * <p/>
+ * source: http://developer.nokia.com/Community/Wiki/Bit_Input/Output_Stream_utility_classes_for_efficient_data_transfer
+ */
+public class BitInputStream {
+ /**
+ * The Java InputStream this class is working on.
+ */
+ private InputStream iIs;
+
+ /**
+ * The buffer containing the currently processed
+ * byte of the input stream.
+ */
+ private int iBuffer;
+
+ /**
+ * Next bit of the current byte value that the user will
+ * get. If it's 8, the next bit will be read from the
+ * next byte of the InputStream.
+ */
+ private int iNextBit = 8;
+
+ /**
+ * Create a new bit input stream based on an existing Java InputStream.
+ *
+ * @param aIs the input stream this class should read the bits from.
+ */
+ public BitInputStream(InputStream aIs) {
+ iIs = aIs;
+ }
+
+ /**
+ * Read a specified number of bits and return them combined as
+ * an integer value. The bits are written to the integer
+ * starting at the highest bit ( << aNumberOfBits ), going down
+ * to the lowest bit ( << 0 )
+ *
+ * @param aNumberOfBits defines how many bits to read from the stream.
+ * @return integer value containing the bits read from the stream.
+ * @throws IOException
+ */
+ synchronized public int readBits(final int aNumberOfBits, boolean stuffIfEnd)
+ throws IOException {
+ int value = 0;
+ for (int i = aNumberOfBits - 1; i >= 0; i--) {
+ value |= (readBit(stuffIfEnd) << i);
+ }
+ return value;
+ }
+
+ synchronized public int available() {
+ try {
+ return (8 - iNextBit) + iIs.available() * 8; // bytestream to bitstream available
+ } catch (Exception e) {
+ return 0;
+ }
+ }
+
+ /**
+ * Read the next bit from the stream.
+ *
+ * @return 0 if the bit is 0, 1 if the bit is 1.
+ * @throws IOException
+ */
+ synchronized public int readBit(boolean stuffIfEnd) throws IOException {
+ if (iIs == null)
+ throw new IOException("Already closed");
+
+ if (iNextBit == 8) {
+ iBuffer = iIs.read();
+
+ if (iBuffer == -1) {
+ if (stuffIfEnd) {
+ return 1;
+ } else {
+ throw new EOFException();
+ }
+ }
+
+ iNextBit = 0;
+ }
+
+ int bit = iBuffer & (1 << iNextBit);
+ iNextBit++;
+
+ bit = (bit == 0) ? 0 : 1;
+
+ return bit;
+ }
+
+ /**
+ * Close the underlying input stream.
+ *
+ * @throws IOException
+ */
+ public void close() throws IOException {
+ iIs.close();
+ iIs = null;
+ }
+} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/experimental/SentenceConfirm.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/experimental/SentenceConfirm.java
new file mode 100644
index 000000000..0374d878c
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/experimental/SentenceConfirm.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2014 Jake McGinty (Open Whisper Systems)
+ *
+ * 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.experimental;
+
+import android.content.Context;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * From https://github.com/mcginty/TextSecure/tree/mnemonic-poem
+ */
+public class SentenceConfirm {
+ Context context;
+ List<String> n, vi, vt, adj, adv, p, art;
+
+ public SentenceConfirm(Context context) {
+ this.context = context;
+ try {
+ n = readFile(R.raw.fp_sentence_nouns);
+ vi = readFile(R.raw.fp_sentence_verbs_i);
+ vt = readFile(R.raw.fp_sentence_verbs_t);
+ adj = readFile(R.raw.fp_sentence_adjectives);
+ adv = readFile(R.raw.fp_sentence_adverbs);
+ p = readFile(R.raw.fp_sentence_prepositions);
+ art = readFile(R.raw.fp_sentence_articles);
+ } catch (IOException e) {
+ Log.e(Constants.TAG, "Reading sentence files failed", e);
+ }
+ }
+
+ List<String> readFile(int resId) throws IOException {
+ if (context.getApplicationContext() == null) {
+ throw new AssertionError("app context can't be null");
+ }
+
+ BufferedReader in = new BufferedReader(new InputStreamReader(
+ context.getApplicationContext()
+ .getResources()
+ .openRawResource(resId)));
+ List<String> words = new ArrayList<>();
+ String word = in.readLine();
+ while (word != null) {
+ words.add(word);
+ word = in.readLine();
+ }
+ in.close();
+ return words;
+ }
+
+ public String fromBytes(final byte[] bytes, int desiredBytes) throws IOException {
+ BitInputStream bin = new BitInputStream(new ByteArrayInputStream(bytes));
+ EntropyString fingerprint = new EntropyString();
+
+ while (fingerprint.getBits() < (desiredBytes * 8)) {
+ if (!fingerprint.isEmpty()) {
+ fingerprint.append("\n\n");
+ }
+ try {
+ fingerprint.append(getSentence(bin));
+ } catch (IOException e) {
+ Log.e(Constants.TAG, "IOException when creating the sentence");
+ throw e;
+ }
+ }
+ return fingerprint.toString();
+ }
+
+ /**
+ * Grab a word for a list of them using the necessary bits to choose from a BitInputStream
+ *
+ * @param words the list of words to select from
+ * @param bin the bit input stream to encode from
+ * @return A Pair of the word and the number of bits consumed from the stream
+ */
+ private EntropyString getWord(List<String> words, BitInputStream bin) throws IOException {
+ final int neededBits = log(words.size(), 2);
+ Log.d(Constants.TAG, "need: " + neededBits + " bits of entropy");
+ Log.d(Constants.TAG, "available: " + bin.available() + " bits");
+ if (neededBits > bin.available()) {
+ Log.d(Constants.TAG, "stuffed with " + (neededBits - bin.available()) + " ones!");
+ }
+ int bits = bin.readBits(neededBits, true);
+ Log.d(Constants.TAG, "got word " + words.get(bits) + " with " + neededBits + " bits of entropy");
+ return new EntropyString(words.get(bits), neededBits);
+ }
+
+ private EntropyString getNounPhrase(BitInputStream bits) throws IOException {
+ final EntropyString phrase = new EntropyString();
+ phrase.append(getWord(art, bits)).append(" ");
+ if (bits.readBit(true) != 0) {
+ phrase.append(getWord(adj, bits)).append(" ");
+ }
+ phrase.incBits();
+
+ phrase.append(getWord(n, bits));
+ Log.d(Constants.TAG, "got phrase " + phrase + " with " + phrase.getBits() + " bits of entropy");
+ return phrase;
+ }
+
+ EntropyString getSentence(BitInputStream bits) throws IOException {
+ final EntropyString sentence = new EntropyString();
+ sentence.append(getNounPhrase(bits)); // Subject
+ if (bits.readBit(true) != 0) {
+ sentence.append(" ").append(getWord(vt, bits)); // Transitive verb
+ sentence.append(" ").append(getNounPhrase(bits)); // Object of transitive verb
+ } else {
+ sentence.append(" ").append(getWord(vi, bits)); // Intransitive verb
+ }
+ sentence.incBits();
+
+ if (bits.readBit(true) != 0) {
+ sentence.append(" ").append(getWord(adv, bits)); // Adverb
+ }
+
+ sentence.incBits();
+ if (bits.readBit(true) != 0) {
+ sentence.append(" ").append(getWord(p, bits)); // Preposition
+ sentence.append(" ").append(getNounPhrase(bits)); // Object of preposition
+ }
+ sentence.incBits();
+ Log.d(Constants.TAG, "got sentence '" + sentence + "' with " + sentence.getBits() + " bits of entropy");
+
+ // uppercase first character, end with dot (without increasing the bits)
+ sentence.getBuilder().replace(0, 1,
+ Character.toString(Character.toUpperCase(sentence.getBuilder().charAt(0))));
+ sentence.getBuilder().append(".");
+
+ return sentence;
+ }
+
+ public static class EntropyString {
+ private StringBuilder builder;
+ private int bits;
+
+ public EntropyString(String phrase, int bits) {
+ this.builder = new StringBuilder(phrase);
+ this.bits = bits;
+ }
+
+ public EntropyString() {
+ this("", 0);
+ }
+
+ public StringBuilder getBuilder() {
+ return builder;
+ }
+
+ public boolean isEmpty() {
+ return builder.length() == 0;
+ }
+
+ public EntropyString append(EntropyString phrase) {
+ builder.append(phrase);
+ bits += phrase.getBits();
+ return this;
+ }
+
+ public EntropyString append(String string) {
+ builder.append(string);
+ return this;
+ }
+
+ public int getBits() {
+ return bits;
+ }
+
+ public void setBits(int bits) {
+ this.bits = bits;
+ }
+
+ public void incBits() {
+ bits += 1;
+ }
+
+ @Override
+ public String toString() {
+ return builder.toString();
+ }
+ }
+
+ private static int log(int x, int base) {
+ return (int) (Math.log(x) / Math.log(base));
+ }
+
+} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ExperimentalWordConfirm.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/experimental/WordConfirm.java
index 43ccac24f..daf63ea9e 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ExperimentalWordConfirm.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/experimental/WordConfirm.java
@@ -15,12 +15,13 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-package org.sufficientlysecure.keychain.ui.util;
+package org.sufficientlysecure.keychain.experimental;
import android.content.Context;
import org.spongycastle.util.Arrays;
import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.util.Log;
import java.io.BufferedReader;
@@ -29,7 +30,7 @@ import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.BitSet;
-public class ExperimentalWordConfirm {
+public class WordConfirm {
public static String getWords(Context context, byte[] fingerprintBlob) {
ArrayList<String> words = new ArrayList<>();
@@ -37,7 +38,7 @@ public class ExperimentalWordConfirm {
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(
- context.getAssets().open("word_confirm_list.txt"),
+ context.getResources().openRawResource(R.raw.fp_word_list),
"UTF-8"
));
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/CloudSearch.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/CloudSearch.java
index d91dd28bc..869d107ab 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/CloudSearch.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/CloudSearch.java
@@ -24,6 +24,9 @@ import java.net.Proxy;
import java.util.ArrayList;
import java.util.Vector;
+import android.support.annotation.NonNull;
+
+
/**
* Search two or more types of server for online keys.
*/
@@ -31,8 +34,8 @@ public class CloudSearch {
private final static long SECONDS = 1000;
- public static ArrayList<ImportKeysListEntry> search(final String query, Preferences.CloudSearchPrefs cloudPrefs,
- final Proxy proxy)
+ public static ArrayList<ImportKeysListEntry> search(
+ @NonNull final String query, Preferences.CloudSearchPrefs cloudPrefs, @NonNull Proxy proxy)
throws Keyserver.CloudSearchFailureException {
final ArrayList<Keyserver> servers = new ArrayList<>();
@@ -40,10 +43,10 @@ public class CloudSearch {
final Vector<Keyserver.CloudSearchFailureException> problems = new Vector<>();
if (cloudPrefs.searchKeyserver) {
- servers.add(new HkpKeyserver(cloudPrefs.keyserver));
+ servers.add(new HkpKeyserver(cloudPrefs.keyserver, proxy));
}
if (cloudPrefs.searchKeybase) {
- servers.add(new KeybaseKeyserver());
+ servers.add(new KeybaseKeyserver(proxy));
}
final ImportKeysList results = new ImportKeysList(servers.size());
@@ -53,7 +56,7 @@ public class CloudSearch {
@Override
public void run() {
try {
- results.addAll(keyserver.search(query, proxy));
+ results.addAll(keyserver.search(query));
} catch (Keyserver.CloudSearchFailureException e) {
problems.add(e);
}
@@ -68,7 +71,7 @@ public class CloudSearch {
// wait for either all the searches to come back, or 10 seconds. If using proxy, wait 30 seconds.
synchronized (results) {
try {
- if (proxy != null) {
+ if (proxy == Proxy.NO_PROXY) {
results.wait(30 * SECONDS);
} else {
results.wait(10 * SECONDS);
@@ -77,7 +80,7 @@ public class CloudSearch {
// kill threads that haven't returned yet
thread.interrupt();
}
- } catch (InterruptedException e) {
+ } catch (InterruptedException ignored) {
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java
index 558b8ce7d..c2190318b 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/HkpKeyserver.java
@@ -23,6 +23,7 @@ import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;
+
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.PgpHelper;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
@@ -45,6 +46,8 @@ import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import android.support.annotation.NonNull;
+
import de.measite.minidns.Client;
import de.measite.minidns.Question;
import de.measite.minidns.Record;
@@ -73,6 +76,7 @@ public class HkpKeyserver extends Keyserver {
private String mHost;
private short mPort;
+ private Proxy mProxy;
private boolean mSecure;
/**
@@ -149,17 +153,17 @@ public class HkpKeyserver extends Keyserver {
* connect using {@link #PORT_DEFAULT}. However, port may be specified after colon
* ("<code>hostname:port</code>", eg. "<code>p80.pool.sks-keyservers.net:80</code>").
*/
- public HkpKeyserver(String hostAndPort) {
+ public HkpKeyserver(String hostAndPort, Proxy proxy) {
String host = hostAndPort;
short port = PORT_DEFAULT;
boolean secure = false;
String[] parts = hostAndPort.split(":");
if (parts.length > 1) {
if (!parts[0].contains(".")) { // This is not a domain or ip, so it must be a protocol name
- if (parts[0].equalsIgnoreCase("hkps") || parts[0].equalsIgnoreCase("https")) {
+ if ("hkps".equalsIgnoreCase(parts[0]) || "https".equalsIgnoreCase(parts[0])) {
secure = true;
port = PORT_DEFAULT_HKPS;
- } else if (!parts[0].equalsIgnoreCase("hkp") && !parts[0].equalsIgnoreCase("http")) {
+ } else if (!"hkp".equalsIgnoreCase(parts[0]) && !"http".equalsIgnoreCase(parts[0])) {
throw new IllegalArgumentException("Protocol " + parts[0] + " is unknown");
}
host = parts[1];
@@ -176,16 +180,18 @@ public class HkpKeyserver extends Keyserver {
}
mHost = host;
mPort = port;
+ mProxy = proxy;
mSecure = secure;
}
- public HkpKeyserver(String host, short port) {
- this(host, port, false);
+ public HkpKeyserver(String host, short port, Proxy proxy) {
+ this(host, port, proxy, false);
}
- public HkpKeyserver(String host, short port, boolean secure) {
+ public HkpKeyserver(String host, short port, Proxy proxy, boolean secure) {
mHost = host;
mPort = port;
+ mProxy = proxy;
mSecure = secure;
}
@@ -196,19 +202,23 @@ public class HkpKeyserver extends Keyserver {
/**
* returns a client with pinned certificate if necessary
*
- * @param url url to be queried by client
+ * @param url url to be queried by client
* @param proxy proxy to be used by client
- * @return client with a pinned certificate if necesary
+ * @return client with a pinned certificate if necessary
*/
public static OkHttpClient getClient(URL url, Proxy proxy) throws IOException {
OkHttpClient client = new OkHttpClient();
try {
- TlsHelper.pinCertificateIfNecessary(client, url);
+ TlsHelper.usePinnedCertificateIfAvailable(client, url);
} catch (TlsHelper.TlsHelperException e) {
Log.w(Constants.TAG, e);
}
+ // don't follow any redirects
+ client.setFollowRedirects(false);
+ client.setFollowSslRedirects(false);
+
if (proxy != null) {
client.setProxy(proxy);
client.setConnectTimeout(30000, TimeUnit.MILLISECONDS);
@@ -221,14 +231,14 @@ public class HkpKeyserver extends Keyserver {
return client;
}
- private String query(String request, Proxy proxy) throws QueryFailedException, HttpError {
+ private String query(String request, @NonNull Proxy proxy) throws QueryFailedException, HttpError {
try {
URL url = new URL(getUrlPrefix() + mHost + ":" + mPort + request);
Log.d(Constants.TAG, "hkp keyserver query: " + url + " Proxy: " + proxy);
OkHttpClient client = getClient(url, proxy);
Response response = client.newCall(new Request.Builder().url(url).build()).execute();
- String responseBody = response.body().string();// contains body both in case of success or failure
+ String responseBody = response.body().string(); // contains body both in case of success or failure
if (response.isSuccessful()) {
return responseBody;
@@ -238,20 +248,15 @@ public class HkpKeyserver extends Keyserver {
} catch (IOException e) {
Log.e(Constants.TAG, "IOException at HkpKeyserver", e);
throw new QueryFailedException("Keyserver '" + mHost + "' is unavailable. Check your Internet connection!" +
- proxy == null?"":" Using proxy " + proxy);
+ (proxy == Proxy.NO_PROXY ? "" : " Using proxy " + proxy));
}
}
/**
* Results are sorted by creation date of key!
- *
- * @param query
- * @return
- * @throws QueryFailedException
- * @throws QueryNeedsRepairException
*/
@Override
- public ArrayList<ImportKeysListEntry> search(String query, Proxy proxy) throws QueryFailedException,
+ public ArrayList<ImportKeysListEntry> search(String query) throws QueryFailedException,
QueryNeedsRepairException {
ArrayList<ImportKeysListEntry> results = new ArrayList<>();
@@ -269,7 +274,7 @@ public class HkpKeyserver extends Keyserver {
String data;
try {
- data = query(request, proxy);
+ data = query(request, mProxy);
} catch (HttpError e) {
if (e.getData() != null) {
Log.d(Constants.TAG, "returned error data: " + e.getData().toLowerCase(Locale.ENGLISH));
@@ -299,30 +304,46 @@ public class HkpKeyserver extends Keyserver {
entry.setQuery(query);
entry.addOrigin(getUrlPrefix() + mHost + ":" + mPort);
- int bitSize = Integer.parseInt(matcher.group(3));
- entry.setBitStrength(bitSize);
- int algorithmId = Integer.decode(matcher.group(2));
- entry.setAlgorithm(KeyFormattingUtils.getAlgorithmInfo(algorithmId, bitSize, null));
-
// group 1 contains the full fingerprint (v4) or the long key id if available
// see https://bitbucket.org/skskeyserver/sks-keyserver/pull-request/12/fixes-for-machine-readable-indexes/diff
String fingerprintOrKeyId = matcher.group(1).toLowerCase(Locale.ENGLISH);
- if (fingerprintOrKeyId.length() > 16) {
+ if (fingerprintOrKeyId.length() == 40) {
entry.setFingerprintHex(fingerprintOrKeyId);
entry.setKeyIdHex("0x" + fingerprintOrKeyId.substring(fingerprintOrKeyId.length()
- 16, fingerprintOrKeyId.length()));
- } else {
+ } else if (fingerprintOrKeyId.length() == 16) {
// set key id only
entry.setKeyIdHex("0x" + fingerprintOrKeyId);
+ } else {
+ Log.e(Constants.TAG, "Wrong length for fingerprint/long key id.");
+ // skip this key
+ continue;
}
- final long creationDate = Long.parseLong(matcher.group(4));
- final GregorianCalendar tmpGreg = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
- tmpGreg.setTimeInMillis(creationDate * 1000);
- entry.setDate(tmpGreg.getTime());
+ try {
+ int bitSize = Integer.parseInt(matcher.group(3));
+ entry.setBitStrength(bitSize);
+ int algorithmId = Integer.decode(matcher.group(2));
+ entry.setAlgorithm(KeyFormattingUtils.getAlgorithmInfo(algorithmId, bitSize, null));
+
+ final long creationDate = Long.parseLong(matcher.group(4));
+ final GregorianCalendar tmpGreg = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
+ tmpGreg.setTimeInMillis(creationDate * 1000);
+ entry.setDate(tmpGreg.getTime());
+ } catch (NumberFormatException e) {
+ Log.e(Constants.TAG, "Conversation for bit size, algorithm, or creation date failed.", e);
+ // skip this key
+ continue;
+ }
- entry.setRevoked(matcher.group(6).contains("r"));
- entry.setExpired(matcher.group(6).contains("e"));
+ try {
+ entry.setRevoked(matcher.group(6).contains("r"));
+ entry.setExpired(matcher.group(6).contains("e"));
+ } catch (NullPointerException e) {
+ Log.e(Constants.TAG, "Check for revocation or expiry failed.", e);
+ // skip this key
+ continue;
+ }
ArrayList<String> userIds = new ArrayList<>();
final String uidLines = matcher.group(7);
@@ -340,6 +361,10 @@ public class HkpKeyserver extends Keyserver {
tmp = URLDecoder.decode(tmp, "UTF8");
} catch (UnsupportedEncodingException ignored) {
// will never happen, because "UTF8" is supported
+ } catch (IllegalArgumentException e) {
+ Log.e(Constants.TAG, "User ID encoding broken", e);
+ // skip this user id
+ continue;
}
}
userIds.add(tmp);
@@ -353,25 +378,28 @@ public class HkpKeyserver extends Keyserver {
}
@Override
- public String get(String keyIdHex, Proxy proxy) throws QueryFailedException {
+ public String get(String keyIdHex) throws QueryFailedException {
String request = "/pks/lookup?op=get&options=mr&search=" + keyIdHex;
- Log.d(Constants.TAG, "hkp keyserver get: " + request + " using Proxy: " + proxy);
+ Log.d(Constants.TAG, "hkp keyserver get: " + request + " using Proxy: " + mProxy);
String data;
try {
- data = query(request, proxy);
+ data = query(request, mProxy);
} catch (HttpError httpError) {
Log.d(Constants.TAG, "Failed to get key at HkpKeyserver", httpError);
throw new QueryFailedException("not found");
}
+ if (data == null) {
+ throw new QueryFailedException("data is null");
+ }
Matcher matcher = PgpHelper.PGP_PUBLIC_KEY.matcher(data);
if (matcher.find()) {
return matcher.group(1);
}
- return null;
+ throw new QueryFailedException("data is null");
}
@Override
- public void add(String armoredKey, Proxy proxy) throws AddKeyException {
+ public void add(String armoredKey) throws AddKeyException {
try {
String path = "/pks/add";
String params;
@@ -382,7 +410,7 @@ public class HkpKeyserver extends Keyserver {
}
URL url = new URL(getUrlPrefix() + mHost + ":" + mPort + path);
- Log.d(Constants.TAG, "hkp keyserver add: " + url.toString());
+ Log.d(Constants.TAG, "hkp keyserver add: " + url);
Log.d(Constants.TAG, "params: " + params);
RequestBody body = RequestBody.create(MediaType.parse("application/x-www-form-urlencoded"), params);
@@ -394,7 +422,7 @@ public class HkpKeyserver extends Keyserver {
.post(body)
.build();
- Response response = getClient(url, proxy).newCall(request).execute();
+ Response response = getClient(url, mProxy).newCall(request).execute();
Log.d(Constants.TAG, "response code: " + response.code());
Log.d(Constants.TAG, "answer: " + response.body().string());
@@ -411,16 +439,15 @@ public class HkpKeyserver extends Keyserver {
@Override
public String toString() {
- return mHost + ":" + mPort;
+ return getUrlPrefix() + mHost + ":" + mPort;
}
/**
* Tries to find a server responsible for a given domain
*
* @return A responsible Keyserver or null if not found.
- * TODO: PHILIP Add proxy functionality
*/
- public static HkpKeyserver resolve(String domain) {
+ public static HkpKeyserver resolve(String domain, Proxy proxy) {
try {
Record[] records = new Client().query(new Question("_hkp._tcp." + domain, Record.TYPE.SRV)).getAnswers();
if (records.length > 0) {
@@ -435,7 +462,7 @@ public class HkpKeyserver extends Keyserver {
Record record = records[0]; // This is our best choice
if (record.getPayload().getType() == Record.TYPE.SRV) {
return new HkpKeyserver(((SRV) record.getPayload()).getName(),
- (short) ((SRV) record.getPayload()).getPort());
+ (short) ((SRV) record.getPayload()).getPort(), proxy);
}
}
} catch (Exception ignored) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java
index c2865410e..e4cd6738b 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java
@@ -19,12 +19,13 @@ package org.sufficientlysecure.keychain.keyimport;
import com.textuality.keybase.lib.KeybaseException;
import com.textuality.keybase.lib.Match;
-import com.textuality.keybase.lib.Search;
+import com.textuality.keybase.lib.KeybaseQuery;
import com.textuality.keybase.lib.User;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.OkHttpKeybaseClient;
import java.net.Proxy;
import java.util.ArrayList;
@@ -32,10 +33,15 @@ import java.util.List;
public class KeybaseKeyserver extends Keyserver {
public static final String ORIGIN = "keybase:keybase.io";
- private String mQuery;
+
+ Proxy mProxy;
+
+ public KeybaseKeyserver(Proxy proxy) {
+ mProxy = proxy;
+ }
@Override
- public ArrayList<ImportKeysListEntry> search(String query, Proxy proxy) throws QueryFailedException,
+ public ArrayList<ImportKeysListEntry> search(String query) throws QueryFailedException,
QueryNeedsRepairException {
ArrayList<ImportKeysListEntry> results = new ArrayList<>();
@@ -46,12 +52,13 @@ public class KeybaseKeyserver extends Keyserver {
if (query.isEmpty()) {
throw new QueryTooShortException();
}
- mQuery = query;
try {
- Iterable<Match> matches = Search.search(query, proxy);
+ KeybaseQuery keybaseQuery = new KeybaseQuery(new OkHttpKeybaseClient());
+ keybaseQuery.setProxy(mProxy);
+ Iterable<Match> matches = keybaseQuery.search(query);
for (Match match : matches) {
- results.add(makeEntry(match));
+ results.add(makeEntry(match, query));
}
} catch (KeybaseException e) {
Log.e(Constants.TAG, "keybase result parsing error", e);
@@ -61,9 +68,9 @@ public class KeybaseKeyserver extends Keyserver {
return results;
}
- private ImportKeysListEntry makeEntry(Match match) throws KeybaseException {
+ private ImportKeysListEntry makeEntry(Match match, String query) throws KeybaseException {
final ImportKeysListEntry entry = new ImportKeysListEntry();
- entry.setQuery(mQuery);
+ entry.setQuery(query);
entry.addOrigin(ORIGIN);
entry.setRevoked(false); // keybase doesn’t say anything about revoked keys
@@ -99,16 +106,18 @@ public class KeybaseKeyserver extends Keyserver {
}
@Override
- public String get(String id, Proxy proxy) throws QueryFailedException {
+ public String get(String id) throws QueryFailedException {
try {
- return User.keyForUsername(id, proxy);
+ KeybaseQuery keybaseQuery = new KeybaseQuery(new OkHttpKeybaseClient());
+ keybaseQuery.setProxy(mProxy);
+ return User.keyForUsername(keybaseQuery, id);
} catch (KeybaseException e) {
throw new QueryFailedException(e.getMessage());
}
}
@Override
- public void add(String armoredKey, Proxy proxy) throws AddKeyException {
+ public void add(String armoredKey) throws AddKeyException {
throw new AddKeyException();
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java
index 640b39f44..00e8d6ac5 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/Keyserver.java
@@ -62,19 +62,19 @@ public abstract class Keyserver {
* query too short _or_ too many responses
*/
public static class QueryTooShortOrTooManyResponsesException extends QueryNeedsRepairException {
- private static final long serialVersionUID = 2703768928624654514L;
+ private static final long serialVersionUID = 2703768928624654518L;
}
public static class AddKeyException extends Exception {
private static final long serialVersionUID = -507574859137295530L;
}
- public abstract List<ImportKeysListEntry> search(String query, Proxy proxy) throws QueryFailedException,
- QueryNeedsRepairException;
+ public abstract List<ImportKeysListEntry> search(String query)
+ throws QueryFailedException, QueryNeedsRepairException;
- public abstract String get(String keyIdHex, Proxy proxy) throws QueryFailedException;
+ public abstract String get(String keyIdHex) throws QueryFailedException;
- public abstract void add(String armoredKey, Proxy proxy) throws AddKeyException;
+ public abstract void add(String armoredKey) throws AddKeyException;
public static String readAll(InputStream in, String encoding) throws IOException {
ByteArrayOutputStream raw = new ByteArrayOutputStream();
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedTokenResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedTokenResource.java
index 998ec3ad4..e5a128e32 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedTokenResource.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedTokenResource.java
@@ -1,5 +1,7 @@
package org.sufficientlysecure.keychain.linked;
+import android.content.Context;
+
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
@@ -8,7 +10,6 @@ import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.json.JSONException;
import org.sufficientlysecure.keychain.Constants;
-import org.sufficientlysecure.keychain.linked.resources.DnsResource;
import org.sufficientlysecure.keychain.linked.resources.GenericHttpsResource;
import org.sufficientlysecure.keychain.linked.resources.GithubResource;
import org.sufficientlysecure.keychain.linked.resources.TwitterResource;
@@ -32,8 +33,6 @@ import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import android.content.Context;
-
public abstract class LinkedTokenResource extends LinkedResource {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BackupOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BackupOperation.java
new file mode 100644
index 000000000..ae9a2c180
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BackupOperation.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2012-2014 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
+ * 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;
+
+
+import java.io.BufferedOutputStream;
+import java.io.DataOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Locale;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+
+import org.spongycastle.bcpg.ArmoredOutputStream;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.operations.results.ExportResult;
+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.pgp.CanonicalizedKeyRing;
+import org.sufficientlysecure.keychain.pgp.PgpSignEncryptInputParcel;
+import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation;
+import org.sufficientlysecure.keychain.pgp.Progressable;
+import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
+import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
+import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
+import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.provider.TemporaryFileProvider;
+import org.sufficientlysecure.keychain.service.BackupKeyringParcel;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
+import org.sufficientlysecure.keychain.util.InputData;
+import org.sufficientlysecure.keychain.util.Log;
+
+
+/**
+ * An operation class which implements high level backup
+ * operations.
+ * This class receives a source and/or destination of keys as input and performs
+ * all steps for this backup.
+ *
+ * @see org.sufficientlysecure.keychain.ui.adapter.ImportKeysAdapter#getSelectedEntries()
+ * For the backup operation, the input consists of a set of key ids and
+ * either the name of a file or an output uri to write to.
+ */
+public class BackupOperation extends BaseOperation<BackupKeyringParcel> {
+
+ private static final String[] PROJECTION = new String[] {
+ KeyRings.MASTER_KEY_ID,
+ KeyRings.PUBKEY_DATA,
+ KeyRings.PRIVKEY_DATA,
+ KeyRings.HAS_ANY_SECRET
+ };
+ private static final int INDEX_MASTER_KEY_ID = 0;
+ private static final int INDEX_PUBKEY_DATA = 1;
+ private static final int INDEX_SECKEY_DATA = 2;
+ private static final int INDEX_HAS_ANY_SECRET = 3;
+
+ public BackupOperation(Context context, ProviderHelper providerHelper, Progressable
+ progressable) {
+ super(context, providerHelper, progressable);
+ }
+
+ public BackupOperation(Context context, ProviderHelper providerHelper,
+ Progressable progressable, AtomicBoolean cancelled) {
+ super(context, providerHelper, progressable, cancelled);
+ }
+
+ @NonNull
+ public ExportResult execute(@NonNull BackupKeyringParcel backupInput, @Nullable CryptoInputParcel cryptoInput) {
+
+ OperationLog log = new OperationLog();
+ if (backupInput.mMasterKeyIds != null) {
+ log.add(LogType.MSG_BACKUP, 0, backupInput.mMasterKeyIds.length);
+ } else {
+ log.add(LogType.MSG_BACKUP_ALL, 0);
+ }
+
+ try {
+
+ boolean nonEncryptedOutput = backupInput.mSymmetricPassphrase == null;
+
+ Uri backupOutputUri = nonEncryptedOutput
+ ? backupInput.mOutputUri
+ : TemporaryFileProvider.createFile(mContext);
+
+ int exportedDataSize;
+
+ { // export key data, and possibly return if we don't encrypt
+
+ DataOutputStream outStream = new DataOutputStream(new BufferedOutputStream(
+ mContext.getContentResolver().openOutputStream(backupOutputUri)));
+
+ boolean backupSuccess = exportKeysToStream(
+ log, backupInput.mMasterKeyIds, backupInput.mExportSecret, outStream);
+
+ exportedDataSize = outStream.size();
+
+ if (!backupSuccess) {
+ // if there was an error, it will be in the log so we just have to return
+ return new ExportResult(ExportResult.RESULT_ERROR, log);
+ }
+
+ if (nonEncryptedOutput) {
+ // log.add(LogType.MSG_EXPORT_NO_ENCRYPT, 1);
+ log.add(LogType.MSG_BACKUP_SUCCESS, 1);
+ return new ExportResult(ExportResult.RESULT_OK, log);
+ }
+ }
+
+ PgpSignEncryptOperation pseOp = new PgpSignEncryptOperation(mContext, mProviderHelper, mProgressable, mCancelled);
+
+ PgpSignEncryptInputParcel inputParcel = new PgpSignEncryptInputParcel();
+ inputParcel.setSymmetricPassphrase(backupInput.mSymmetricPassphrase);
+ inputParcel.setEnableAsciiArmorOutput(true);
+ inputParcel.setAddBackupHeader(true);
+
+ InputStream inStream = mContext.getContentResolver().openInputStream(backupOutputUri);
+
+ String filename;
+ if (backupInput.mMasterKeyIds != null && backupInput.mMasterKeyIds.length == 1) {
+ filename = Constants.FILE_BACKUP_PREFIX + KeyFormattingUtils.convertKeyIdToHex(backupInput.mMasterKeyIds[0]);
+ } else {
+ filename = Constants.FILE_BACKUP_PREFIX + new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date());
+ }
+ filename += backupInput.mExportSecret ? Constants.FILE_EXTENSION_BACKUP_SECRET : Constants.FILE_EXTENSION_BACKUP_PUBLIC;
+
+ InputData inputData = new InputData(inStream, exportedDataSize, filename);
+
+ OutputStream outStream = mContext.getContentResolver().openOutputStream(backupInput.mOutputUri);
+ outStream = new BufferedOutputStream(outStream);
+
+ PgpSignEncryptResult encryptResult = pseOp.execute(inputParcel, new CryptoInputParcel(), inputData, outStream);
+ if (!encryptResult.success()) {
+ log.addByMerge(encryptResult, 1);
+ // log.add(LogType.MSG_EXPORT_ERROR_ENCRYPT, 1);
+ return new ExportResult(ExportResult.RESULT_ERROR, log);
+ }
+
+ log.add(encryptResult, 1);
+ log.add(LogType.MSG_BACKUP_SUCCESS, 1);
+ return new ExportResult(ExportResult.RESULT_OK, log);
+
+ } catch (FileNotFoundException e) {
+ log.add(LogType.MSG_BACKUP_ERROR_URI_OPEN, 1);
+ return new ExportResult(ExportResult.RESULT_ERROR, log);
+
+ }
+
+ }
+
+ boolean exportKeysToStream(OperationLog log, long[] masterKeyIds, boolean exportSecret, OutputStream outStream) {
+
+ // noinspection unused TODO use these in a log entry
+ int okSecret = 0, okPublic = 0;
+
+ int progress = 0;
+
+ Cursor cursor = queryForKeys(masterKeyIds);
+
+ if (cursor == null || !cursor.moveToFirst()) {
+ log.add(LogType.MSG_BACKUP_ERROR_DB, 1);
+ return false; // new ExportResult(ExportResult.RESULT_ERROR, log);
+ }
+
+ try {
+
+ int numKeys = cursor.getCount();
+
+ updateProgress(mContext.getResources().getQuantityString(R.plurals.progress_exporting_key, numKeys),
+ 0, numKeys);
+
+ // For each public masterKey id
+ while (!cursor.isAfterLast()) {
+
+ long keyId = cursor.getLong(INDEX_MASTER_KEY_ID);
+ log.add(LogType.MSG_BACKUP_PUBLIC, 1, KeyFormattingUtils.beautifyKeyId(keyId));
+
+ if (writePublicKeyToStream(log, outStream, cursor)) {
+ okPublic += 1;
+
+ boolean hasSecret = cursor.getInt(INDEX_HAS_ANY_SECRET) > 0;
+ if (exportSecret && hasSecret) {
+ log.add(LogType.MSG_BACKUP_SECRET, 2, KeyFormattingUtils.beautifyKeyId(keyId));
+ if (writeSecretKeyToStream(log, outStream, cursor)) {
+ okSecret += 1;
+ }
+ }
+ }
+
+ updateProgress(progress++, numKeys);
+ cursor.moveToNext();
+ }
+
+ updateProgress(R.string.progress_done, numKeys, numKeys);
+
+ } catch (IOException e) {
+ log.add(LogType.MSG_BACKUP_ERROR_IO, 1);
+ return false; // new ExportResult(ExportResult.RESULT_ERROR, log);
+ } finally {
+ // Make sure the stream is closed
+ if (outStream != null) try {
+ outStream.close();
+ } catch (Exception e) {
+ Log.e(Constants.TAG, "error closing stream", e);
+ }
+ cursor.close();
+ }
+
+ return true;
+
+ }
+
+ private boolean writePublicKeyToStream(OperationLog log, OutputStream outStream, Cursor cursor)
+ throws IOException {
+
+ ArmoredOutputStream arOutStream = null;
+
+ try {
+ arOutStream = new ArmoredOutputStream(outStream);
+ byte[] data = cursor.getBlob(INDEX_PUBKEY_DATA);
+ CanonicalizedKeyRing ring = UncachedKeyRing.decodeFromData(data).canonicalize(log, 2, true);
+ ring.encode(arOutStream);
+
+ } catch (PgpGeneralException e) {
+ log.add(LogType.MSG_UPLOAD_ERROR_IO, 2);
+ } finally {
+ if (arOutStream != null) {
+ arOutStream.close();
+ }
+ }
+ return true;
+ }
+
+ private boolean writeSecretKeyToStream(OperationLog log, OutputStream outStream, Cursor cursor)
+ throws IOException {
+
+ ArmoredOutputStream arOutStream = null;
+
+ try {
+ arOutStream = new ArmoredOutputStream(outStream);
+ byte[] data = cursor.getBlob(INDEX_SECKEY_DATA);
+ CanonicalizedKeyRing ring = UncachedKeyRing.decodeFromData(data).canonicalize(log, 2, true);
+ ring.encode(arOutStream);
+
+ } catch (PgpGeneralException e) {
+ log.add(LogType.MSG_UPLOAD_ERROR_IO, 2);
+ } finally {
+ if (arOutStream != null) {
+ arOutStream.close();
+ }
+ }
+ return true;
+ }
+
+ private Cursor queryForKeys(long[] masterKeyIds) {
+
+ String selection = null, selectionArgs[] = null;
+
+ if (masterKeyIds != null) {
+ // convert long[] to String[]
+ selectionArgs = new String[masterKeyIds.length];
+ for (int i = 0; i < masterKeyIds.length; i++) {
+ selectionArgs[i] = Long.toString(masterKeyIds[i]);
+ }
+
+ // generates ?,?,? as placeholders for selectionArgs
+ String placeholders = TextUtils.join(",",
+ Collections.nCopies(masterKeyIds.length, "?"));
+
+ // put together selection string
+ selection = Tables.KEYS + "." + KeyRings.MASTER_KEY_ID
+ + " IN (" + placeholders + ")";
+ }
+
+ return mProviderHelper.getContentResolver().query(
+ KeyRings.buildUnifiedKeyRingsUri(), PROJECTION, selection, selectionArgs,
+ Tables.KEYS + "." + KeyRings.MASTER_KEY_ID
+ );
+
+ }
+
+} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java
index e4026eaaf..99d1768b1 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BaseOperation.java
@@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.operations;
import android.content.Context;
import android.os.Parcelable;
import android.support.annotation.NonNull;
+import android.support.annotation.StringRes;
import org.sufficientlysecure.keychain.Constants.key;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
@@ -81,7 +82,7 @@ public abstract class BaseOperation <T extends Parcelable> implements Passphrase
@NonNull
public abstract OperationResult execute(T input, CryptoInputParcel cryptoInput);
- public void updateProgress(int message, int current, int total) {
+ public void updateProgress(@StringRes int message, int current, int total) {
if (mProgressable != null) {
mProgressable.setProgress(message, current, total);
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BenchmarkOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BenchmarkOperation.java
new file mode 100644
index 000000000..f6e157c74
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/BenchmarkOperation.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
+ * Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.operations;
+
+
+import java.util.Random;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+
+import org.spongycastle.bcpg.HashAlgorithmTags;
+import org.spongycastle.bcpg.S2K;
+import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.operator.PBEDataDecryptorFactory;
+import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider;
+import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
+import org.spongycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.operations.results.BenchmarkResult;
+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.operations.results.SignEncryptResult;
+import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
+import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation;
+import org.sufficientlysecure.keychain.pgp.Progressable;
+import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.service.BenchmarkInputParcel;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.Passphrase;
+import org.sufficientlysecure.keychain.util.ProgressScaler;
+
+
+public class BenchmarkOperation extends BaseOperation<BenchmarkInputParcel> {
+
+ public BenchmarkOperation(Context context, ProviderHelper providerHelper, Progressable
+ progressable) {
+ super(context, providerHelper, progressable);
+ }
+
+ @NonNull
+ @Override
+ public BenchmarkResult execute(BenchmarkInputParcel consolidateInputParcel,
+ CryptoInputParcel cryptoInputParcel) {
+ OperationLog log = new OperationLog();
+ log.add(LogType.MSG_BENCH, 0);
+
+ // random data
+ byte[] buf = new byte[1024*1024*5];
+ new Random().nextBytes(buf);
+
+ Passphrase passphrase = new Passphrase("a");
+
+ int numRepeats = 5;
+ long totalTime = 0;
+
+ // encrypt
+ SignEncryptResult encryptResult;
+ int i = 0;
+ do {
+ SignEncryptOperation op =
+ new SignEncryptOperation(mContext, mProviderHelper,
+ new ProgressScaler(mProgressable, i*(50/numRepeats), (i+1)*(50/numRepeats), 100), mCancelled);
+ SignEncryptParcel input = new SignEncryptParcel();
+ input.setSymmetricPassphrase(passphrase);
+ input.setBytes(buf);
+ encryptResult = op.execute(input, new CryptoInputParcel());
+ log.add(encryptResult, 1);
+ log.add(LogType.MSG_BENCH_ENC_TIME, 2,
+ String.format("%.2f", encryptResult.getResults().get(0).mOperationTime / 1000.0));
+ totalTime += encryptResult.getResults().get(0).mOperationTime;
+ } while (++i < numRepeats);
+
+ long encryptionTime = totalTime / numRepeats;
+ totalTime = 0;
+
+ // decrypt
+ i = 0;
+ do {
+ DecryptVerifyResult decryptResult;
+ PgpDecryptVerifyOperation op =
+ new PgpDecryptVerifyOperation(mContext, mProviderHelper,
+ new ProgressScaler(mProgressable, 50 +i*(50/numRepeats), 50 +(i+1)*(50/numRepeats), 100));
+ PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(encryptResult.getResultBytes());
+ input.setAllowSymmetricDecryption(true);
+ decryptResult = op.execute(input, new CryptoInputParcel(passphrase));
+ log.add(decryptResult, 1);
+ log.add(LogType.MSG_BENCH_DEC_TIME, 2, String.format("%.2f", decryptResult.mOperationTime / 1000.0));
+ totalTime += decryptResult.mOperationTime;
+ } while (++i < numRepeats);
+
+ long decryptionTime = totalTime / numRepeats;
+ totalTime = 0;
+
+ int iterationsFor100ms;
+ try {
+ PGPDigestCalculatorProvider digestCalcProvider = new JcaPGPDigestCalculatorProviderBuilder()
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build();
+ PBEDataDecryptorFactory decryptorFactory = new JcePBEDataDecryptorFactoryBuilder(
+ digestCalcProvider).setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
+ "".toCharArray());
+
+ byte[] iv = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };
+ int iterations = 0;
+ while (iterations < 255 && totalTime < 100) {
+ iterations += 1;
+
+ S2K s2k = new S2K(HashAlgorithmTags.SHA1, iv, iterations);
+ totalTime = System.currentTimeMillis();
+ decryptorFactory.makeKeyFromPassPhrase(SymmetricKeyAlgorithmTags.AES_128, s2k);
+ totalTime = System.currentTimeMillis() -totalTime;
+
+ if ((iterations % 10) == 0) {
+ log.add(LogType.MSG_BENCH_S2K_FOR_IT, 1, Integer.toString(iterations), Long.toString(totalTime));
+ }
+
+ }
+ iterationsFor100ms = iterations;
+
+ } catch (PGPException e) {
+ Log.e(Constants.TAG, "internal error during benchmark", e);
+ log.add(LogType.MSG_INTERNAL_ERROR, 0);
+ return new BenchmarkResult(BenchmarkResult.RESULT_ERROR, log);
+ }
+
+ log.add(LogType.MSG_BENCH_S2K_100MS_ITS, 1, Integer.toString(iterationsFor100ms));
+ log.add(LogType.MSG_BENCH_ENC_TIME_AVG, 1, String.format("%.2f", encryptionTime/1000.0));
+ log.add(LogType.MSG_BENCH_DEC_TIME_AVG, 1, String.format("%.2f", decryptionTime/1000.0));
+
+ log.add(LogType.MSG_BENCH_SUCCESS, 0);
+ return new BenchmarkResult(BenchmarkResult.RESULT_OK, log);
+ }
+
+}
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 eeed24db0..7d11fa1f1 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java
@@ -17,15 +17,20 @@
package org.sufficientlysecure.keychain.operations;
+
+import java.net.Proxy;
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicBoolean;
+
import android.content.Context;
import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
import org.sufficientlysecure.keychain.operations.results.CertifyResult;
-import org.sufficientlysecure.keychain.operations.results.ExportResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
+import org.sufficientlysecure.keychain.operations.results.UploadResult;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
@@ -40,6 +45,7 @@ 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.UploadKeyringParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder;
@@ -48,10 +54,6 @@ import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.Preferences;
import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
-import java.net.Proxy;
-import java.util.ArrayList;
-import java.util.concurrent.atomic.AtomicBoolean;
-
/**
* An operation which implements a high level user id certification operation.
* <p/>
@@ -204,23 +206,9 @@ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> {
}
// these variables are used inside the following loop, but they need to be created only once
- HkpKeyserver keyServer = null;
- ExportOperation exportOperation = null;
- Proxy proxy = null;
+ UploadOperation uploadOperation = null;
if (parcel.keyServerUri != null) {
- keyServer = new HkpKeyserver(parcel.keyServerUri);
- exportOperation = new ExportOperation(mContext, mProviderHelper, mProgressable);
- if (cryptoInput.getParcelableProxy() == null) {
- // explicit proxy not set
- if (!OrbotHelper.isOrbotInRequiredState(mContext)) {
- return new CertifyResult(null,
- RequiredInputParcel.createOrbotRequiredOperation(), cryptoInput);
- }
- proxy = Preferences.getPreferences(mContext).getProxyPrefs()
- .parcelableProxy.getProxy();
- } else {
- proxy = cryptoInput.getParcelableProxy().getProxy();
- }
+ uploadOperation = new UploadOperation(mContext, mProviderHelper, mProgressable, mCancelled);
}
// Write all certified keys into the database
@@ -239,11 +227,10 @@ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> {
mProviderHelper.clearLog();
SaveKeyringResult result = mProviderHelper.savePublicKeyRing(certifiedKey);
- if (exportOperation != null) {
- ExportResult uploadResult = exportOperation.uploadKeyRingToServer(
- keyServer,
- certifiedKey,
- proxy);
+ if (uploadOperation != null) {
+ UploadKeyringParcel uploadInput =
+ new UploadKeyringParcel(parcel.keyServerUri, certifiedKey.getMasterKeyId());
+ UploadResult uploadResult = uploadOperation.execute(uploadInput, cryptoInput);
log.add(uploadResult, 2);
if (uploadResult.success()) {
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 f5ba88502..3b2c484be 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java
@@ -17,17 +17,20 @@
package org.sufficientlysecure.keychain.operations;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
import android.content.Context;
import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
-import org.sufficientlysecure.keychain.operations.results.ExportResult;
-import org.sufficientlysecure.keychain.operations.results.InputPendingResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult;
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
+import org.sufficientlysecure.keychain.operations.results.UploadResult;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
import org.sufficientlysecure.keychain.pgp.Progressable;
@@ -35,17 +38,14 @@ import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
import org.sufficientlysecure.keychain.service.ContactSyncAdapterService;
-import org.sufficientlysecure.keychain.service.ExportKeyringParcel;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
+import org.sufficientlysecure.keychain.service.UploadKeyringParcel;
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.ProgressScaler;
-import java.io.IOException;
-import java.util.concurrent.atomic.AtomicBoolean;
-
/**
* An operation which implements a high level key edit operation.
* <p/>
@@ -72,7 +72,7 @@ public class EditKeyOperation extends BaseOperation<SaveKeyringParcel> {
* @return the result of the operation
*/
@NonNull
- public InputPendingResult execute(SaveKeyringParcel saveParcel, CryptoInputParcel cryptoInput) {
+ public EditKeyResult execute(SaveKeyringParcel saveParcel, CryptoInputParcel cryptoInput) {
OperationLog log = new OperationLog();
log.add(LogType.MSG_ED, 0);
@@ -99,7 +99,8 @@ public class EditKeyOperation extends BaseOperation<SaveKeyringParcel> {
modifyResult = keyOperations.modifySecretKeyRing(secRing, cryptoInput, saveParcel);
if (modifyResult.isPending()) {
- return modifyResult;
+ log.add(modifyResult, 1);
+ return new EditKeyResult(log, modifyResult);
}
} catch (NotFoundException e) {
@@ -134,32 +135,29 @@ public class EditKeyOperation extends BaseOperation<SaveKeyringParcel> {
UncachedKeyRing ring = modifyResult.getRing();
if (saveParcel.isUpload()) {
- UncachedKeyRing publicKeyRing;
+ byte[] keyringBytes;
try {
- publicKeyRing = ring.extractPublicKeyRing();
+ UncachedKeyRing publicKeyRing = ring.extractPublicKeyRing();
+ keyringBytes = publicKeyRing.getEncoded();
} catch (IOException e) {
log.add(LogType.MSG_ED_ERROR_EXTRACTING_PUBLIC_UPLOAD, 1);
return new EditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
}
- ExportKeyringParcel exportKeyringParcel =
- new ExportKeyringParcel(saveParcel.getUploadKeyserver(),
- publicKeyRing);
+ UploadKeyringParcel exportKeyringParcel =
+ new UploadKeyringParcel(saveParcel.getUploadKeyserver(), keyringBytes);
- ExportResult uploadResult =
- new ExportOperation(mContext, mProviderHelper, mProgressable)
+ UploadResult uploadResult =
+ new UploadOperation(mContext, mProviderHelper, mProgressable, mCancelled)
.execute(exportKeyringParcel, cryptoInput);
+ log.add(uploadResult, 2);
+
if (uploadResult.isPending()) {
- return uploadResult;
+ return new EditKeyResult(log, uploadResult);
} else if (!uploadResult.success() && saveParcel.isUploadAtomic()) {
// if atomic, update fail implies edit operation should also fail and not save
- log.add(uploadResult, 2);
- return new EditKeyResult(log, RequiredInputParcel.createRetryUploadOperation(),
- cryptoInput);
- } else {
- // upload succeeded or not atomic so we continue
- log.add(uploadResult, 2);
+ return new EditKeyResult(log, RequiredInputParcel.createRetryUploadOperation(), cryptoInput);
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ExportOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ExportOperation.java
deleted file mode 100644
index 531ac01f2..000000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ExportOperation.java
+++ /dev/null
@@ -1,385 +0,0 @@
-/*
- * Copyright (C) 2012-2014 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
- * 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;
-
-import java.io.BufferedOutputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.net.Proxy;
-import java.util.Collections;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.support.annotation.NonNull;
-import android.text.TextUtils;
-
-import org.spongycastle.bcpg.ArmoredOutputStream;
-import org.sufficientlysecure.keychain.Constants;
-import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
-import org.sufficientlysecure.keychain.keyimport.Keyserver.AddKeyException;
-import org.sufficientlysecure.keychain.operations.results.ExportResult;
-import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
-import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
-import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing;
-import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
-import org.sufficientlysecure.keychain.pgp.Progressable;
-import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
-import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
-import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
-import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
-import org.sufficientlysecure.keychain.provider.ProviderHelper;
-import org.sufficientlysecure.keychain.service.ExportKeyringParcel;
-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.FileHelper;
-import org.sufficientlysecure.keychain.util.Log;
-import org.sufficientlysecure.keychain.util.Preferences;
-import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
-
-/**
- * An operation class which implements high level export
- * operations.
- * This class receives a source and/or destination of keys as input and performs
- * all steps for this export.
- *
- * @see org.sufficientlysecure.keychain.ui.adapter.ImportKeysAdapter#getSelectedEntries()
- * For the export operation, the input consists of a set of key ids and
- * either the name of a file or an output uri to write to.
- */
-public class ExportOperation extends BaseOperation<ExportKeyringParcel> {
-
- public ExportOperation(Context context, ProviderHelper providerHelper, Progressable
- progressable) {
- super(context, providerHelper, progressable);
- }
-
- public ExportOperation(Context context, ProviderHelper providerHelper,
- Progressable progressable, AtomicBoolean cancelled) {
- super(context, providerHelper, progressable, cancelled);
- }
-
- public ExportResult uploadKeyRingToServer(HkpKeyserver server, CanonicalizedPublicKeyRing keyring,
- Proxy proxy) {
- return uploadKeyRingToServer(server, keyring.getUncachedKeyRing(), proxy);
- }
-
- public ExportResult uploadKeyRingToServer(HkpKeyserver server, UncachedKeyRing keyring, Proxy proxy) {
- mProgressable.setProgress(R.string.progress_uploading, 0, 1);
-
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- ArmoredOutputStream aos = null;
- OperationLog log = new OperationLog();
- log.add(LogType.MSG_EXPORT_UPLOAD_PUBLIC, 0, KeyFormattingUtils.convertKeyIdToHex(
- keyring.getPublicKey().getKeyId()
- ));
-
- try {
- aos = new ArmoredOutputStream(bos);
- keyring.encode(aos);
- aos.close();
-
- String armoredKey = bos.toString("UTF-8");
- server.add(armoredKey, proxy);
-
- log.add(LogType.MSG_EXPORT_UPLOAD_SUCCESS, 1);
- return new ExportResult(ExportResult.RESULT_OK, log);
- } catch (IOException e) {
- Log.e(Constants.TAG, "IOException", e);
-
- log.add(LogType.MSG_EXPORT_ERROR_KEY, 1);
- return new ExportResult(ExportResult.RESULT_ERROR, log);
- } catch (AddKeyException e) {
- Log.e(Constants.TAG, "AddKeyException", e);
-
- log.add(LogType.MSG_EXPORT_ERROR_UPLOAD, 1);
- return new ExportResult(ExportResult.RESULT_ERROR, log);
- } finally {
- mProgressable.setProgress(R.string.progress_uploading, 1, 1);
- try {
- if (aos != null) {
- aos.close();
- }
- bos.close();
- } catch (IOException e) {
- // this is just a finally thing, no matter if it doesn't work out.
- }
- }
- }
-
- public ExportResult exportToFile(long[] masterKeyIds, boolean exportSecret, String outputFile) {
-
- OperationLog log = new OperationLog();
- if (masterKeyIds != null) {
- log.add(LogType.MSG_EXPORT, 0, masterKeyIds.length);
- } else {
- log.add(LogType.MSG_EXPORT_ALL, 0);
- }
-
- // do we have a file name?
- if (outputFile == null) {
- log.add(LogType.MSG_EXPORT_ERROR_NO_FILE, 1);
- return new ExportResult(ExportResult.RESULT_ERROR, log);
- }
-
- log.add(LogType.MSG_EXPORT_FILE_NAME, 1, outputFile);
-
- // check if storage is ready
- if (!FileHelper.isStorageMounted(outputFile)) {
- log.add(LogType.MSG_EXPORT_ERROR_STORAGE, 1);
- return new ExportResult(ExportResult.RESULT_ERROR, log);
- }
-
- try {
- OutputStream outStream = new FileOutputStream(outputFile);
- try {
- ExportResult result = exportKeyRings(log, masterKeyIds, exportSecret, outStream);
- if (result.cancelled()) {
- //noinspection ResultOfMethodCallIgnored
- new File(outputFile).delete();
- }
- return result;
- } finally {
- outStream.close();
- }
- } catch (IOException e) {
- log.add(LogType.MSG_EXPORT_ERROR_FOPEN, 1);
- return new ExportResult(ExportResult.RESULT_ERROR, log);
- }
-
- }
-
- public ExportResult exportToUri(long[] masterKeyIds, boolean exportSecret, Uri outputUri) {
-
- OperationLog log = new OperationLog();
- if (masterKeyIds != null) {
- log.add(LogType.MSG_EXPORT, 0, masterKeyIds.length);
- } else {
- log.add(LogType.MSG_EXPORT_ALL, 0);
- }
-
- // do we have a file name?
- if (outputUri == null) {
- log.add(LogType.MSG_EXPORT_ERROR_NO_URI, 1);
- return new ExportResult(ExportResult.RESULT_ERROR, log);
- }
-
- try {
- OutputStream outStream = mProviderHelper.getContentResolver().openOutputStream
- (outputUri);
- return exportKeyRings(log, masterKeyIds, exportSecret, outStream);
- } catch (FileNotFoundException e) {
- log.add(LogType.MSG_EXPORT_ERROR_URI_OPEN, 1);
- return new ExportResult(ExportResult.RESULT_ERROR, log);
- }
-
- }
-
- ExportResult exportKeyRings(OperationLog log, long[] masterKeyIds, boolean exportSecret,
- OutputStream outStream) {
-
- /* TODO isn't this checked above, with the isStorageMounted call?
- if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
- log.add(LogType.MSG_EXPORT_ERROR_STORAGE, 1);
- return new ExportResult(ExportResult.RESULT_ERROR, log);
- }
- */
-
- if (!BufferedOutputStream.class.isInstance(outStream)) {
- outStream = new BufferedOutputStream(outStream);
- }
-
- int okSecret = 0, okPublic = 0, progress = 0;
-
- Cursor cursor = null;
- try {
-
- String selection = null, selectionArgs[] = null;
-
- if (masterKeyIds != null) {
- // convert long[] to String[]
- selectionArgs = new String[masterKeyIds.length];
- for (int i = 0; i < masterKeyIds.length; i++) {
- selectionArgs[i] = Long.toString(masterKeyIds[i]);
- }
-
- // generates ?,?,? as placeholders for selectionArgs
- String placeholders = TextUtils.join(",",
- Collections.nCopies(masterKeyIds.length, "?"));
-
- // put together selection string
- selection = Tables.KEYS + "." + KeyRings.MASTER_KEY_ID
- + " IN (" + placeholders + ")";
- }
-
- cursor = mProviderHelper.getContentResolver().query(
- KeyRings.buildUnifiedKeyRingsUri(), new String[]{
- KeyRings.MASTER_KEY_ID, KeyRings.PUBKEY_DATA,
- KeyRings.PRIVKEY_DATA, KeyRings.HAS_ANY_SECRET
- }, selection, selectionArgs, Tables.KEYS + "." + KeyRings.MASTER_KEY_ID
- );
-
- if (cursor == null || !cursor.moveToFirst()) {
- log.add(LogType.MSG_EXPORT_ERROR_DB, 1);
- return new ExportResult(ExportResult.RESULT_ERROR, log, okPublic, okSecret);
- }
-
- int numKeys = cursor.getCount();
-
- updateProgress(
- mContext.getResources().getQuantityString(R.plurals.progress_exporting_key,
- numKeys), 0, numKeys);
-
- // For each public masterKey id
- while (!cursor.isAfterLast()) {
-
- long keyId = cursor.getLong(0);
- ArmoredOutputStream arOutStream = null;
-
- // Create an output stream
- try {
- arOutStream = new ArmoredOutputStream(outStream);
-
- log.add(LogType.MSG_EXPORT_PUBLIC, 1, KeyFormattingUtils.beautifyKeyId(keyId));
-
- byte[] data = cursor.getBlob(1);
- CanonicalizedKeyRing ring =
- UncachedKeyRing.decodeFromData(data).canonicalize(log, 2, true);
- ring.encode(arOutStream);
-
- okPublic += 1;
- } catch (PgpGeneralException e) {
- log.add(LogType.MSG_EXPORT_ERROR_KEY, 2);
- updateProgress(progress++, numKeys);
- continue;
- } finally {
- // make sure this is closed
- if (arOutStream != null) {
- arOutStream.close();
- }
- arOutStream = null;
- }
-
- if (exportSecret && cursor.getInt(3) > 0) {
- try {
- arOutStream = new ArmoredOutputStream(outStream);
-
- // export secret key part
- log.add(LogType.MSG_EXPORT_SECRET, 2, KeyFormattingUtils.beautifyKeyId
- (keyId));
- byte[] data = cursor.getBlob(2);
- CanonicalizedKeyRing ring =
- UncachedKeyRing.decodeFromData(data).canonicalize(log, 2, true);
- ring.encode(arOutStream);
-
- okSecret += 1;
- } catch (PgpGeneralException e) {
- log.add(LogType.MSG_EXPORT_ERROR_KEY, 2);
- updateProgress(progress++, numKeys);
- continue;
- } finally {
- // make sure this is closed
- if (arOutStream != null) {
- arOutStream.close();
- }
- }
- }
-
- updateProgress(progress++, numKeys);
-
- cursor.moveToNext();
- }
-
- updateProgress(R.string.progress_done, numKeys, numKeys);
-
- } catch (IOException e) {
- log.add(LogType.MSG_EXPORT_ERROR_IO, 1);
- return new ExportResult(ExportResult.RESULT_ERROR, log, okPublic, okSecret);
- } finally {
- // Make sure the stream is closed
- if (outStream != null) try {
- outStream.close();
- } catch (Exception e) {
- Log.e(Constants.TAG, "error closing stream", e);
- }
- if (cursor != null) {
- cursor.close();
- }
- }
-
-
- log.add(LogType.MSG_EXPORT_SUCCESS, 1);
- return new ExportResult(ExportResult.RESULT_OK, log, okPublic, okSecret);
-
- }
-
- @NonNull
- public ExportResult execute(ExportKeyringParcel exportInput, CryptoInputParcel cryptoInput) {
- switch (exportInput.mExportType) {
- case UPLOAD_KEYSERVER: {
- Proxy proxy;
- if (cryptoInput.getParcelableProxy() == null) {
- // explicit proxy not set
- if (!OrbotHelper.isOrbotInRequiredState(mContext)) {
- return new ExportResult(null,
- RequiredInputParcel.createOrbotRequiredOperation(), cryptoInput);
- }
- proxy = Preferences.getPreferences(mContext).getProxyPrefs()
- .parcelableProxy.getProxy();
- } else {
- proxy = cryptoInput.getParcelableProxy().getProxy();
- }
-
- HkpKeyserver hkpKeyserver = new HkpKeyserver(exportInput.mKeyserver);
- try {
- if (exportInput.mCanonicalizedPublicKeyringUri != null) {
- CanonicalizedPublicKeyRing keyring
- = mProviderHelper.getCanonicalizedPublicKeyRing(
- exportInput.mCanonicalizedPublicKeyringUri);
- return uploadKeyRingToServer(hkpKeyserver, keyring, proxy);
- } else {
- return uploadKeyRingToServer(hkpKeyserver, exportInput.mUncachedKeyRing,
- proxy);
- }
- } catch (ProviderHelper.NotFoundException e) {
- Log.e(Constants.TAG, "error uploading key", e);
- return new ExportResult(ExportResult.RESULT_ERROR, new OperationLog());
- }
- }
- case EXPORT_FILE: {
- return exportToFile(exportInput.mMasterKeyIds, exportInput.mExportSecret,
- exportInput.mOutputFile);
- }
- case EXPORT_URI: {
- return exportToUri(exportInput.mMasterKeyIds, exportInput.mExportSecret,
- exportInput.mOutputUri);
- }
- default: { // can never happen, all enum types must be handled above
- throw new AssertionError("must not happen, this is a bug!");
- }
- }
- }
-} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java
index 89575338f..19a05790f 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/ImportOperation.java
@@ -28,7 +28,7 @@ import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
-import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -82,6 +82,8 @@ import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
*/
public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
+ public static final int MAX_THREADS = 10;
+
public ImportOperation(Context context, ProviderHelper providerHelper, Progressable
progressable) {
super(context, providerHelper, progressable);
@@ -133,7 +135,7 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
@NonNull
private ImportKeyResult serialKeyRingImport(Iterator<ParcelableKeyRing> entries, int num,
String keyServerUri, Progressable progressable,
- Proxy proxy) {
+ @NonNull Proxy proxy) {
if (progressable != null) {
progressable.setProgress(R.string.progress_importing, 0, 100);
}
@@ -188,7 +190,7 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
// Make sure we have the keyserver instance cached
if (keyServer == null) {
log.add(LogType.MSG_IMPORT_KEYSERVER, 1, keyServerUri);
- keyServer = new HkpKeyserver(keyServerUri);
+ keyServer = new HkpKeyserver(keyServerUri, proxy);
}
try {
@@ -197,11 +199,10 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
if (entry.mExpectedFingerprint != null) {
log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER, 2, "0x" +
entry.mExpectedFingerprint.substring(24));
- data = keyServer.get("0x" + entry.mExpectedFingerprint, proxy)
- .getBytes();
+ data = keyServer.get("0x" + entry.mExpectedFingerprint).getBytes();
} else {
log.add(LogType.MSG_IMPORT_FETCH_KEYSERVER, 2, entry.mKeyIdHex);
- data = keyServer.get(entry.mKeyIdHex, proxy).getBytes();
+ data = keyServer.get(entry.mKeyIdHex).getBytes();
}
key = UncachedKeyRing.decodeFromData(data);
if (key != null) {
@@ -219,12 +220,12 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
if (entry.mKeybaseName != null) {
// Make sure we have this cached
if (keybaseServer == null) {
- keybaseServer = new KeybaseKeyserver();
+ keybaseServer = new KeybaseKeyserver(proxy);
}
try {
log.add(LogType.MSG_IMPORT_FETCH_KEYBASE, 2, entry.mKeybaseName);
- byte[] data = keybaseServer.get(entry.mKeybaseName, proxy).getBytes();
+ byte[] data = keybaseServer.get(entry.mKeybaseName).getBytes();
UncachedKeyRing keybaseKey = UncachedKeyRing.decodeFromData(data);
// If there already is a key, merge the two
@@ -261,12 +262,6 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
continue;
}
- // Another check if we have been cancelled
- if (checkCancelled()) {
- cancelled = true;
- break;
- }
-
SaveKeyringResult result;
// synchronizing prevents https://github.com/open-keychain/open-keychain/issues/1221
// and https://github.com/open-keychain/open-keychain/issues/1480
@@ -365,13 +360,15 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
}
}
- // Final log entry, it's easier to do this individually
- if ((newKeys > 0 || updatedKeys > 0) && badKeys > 0) {
- log.add(LogType.MSG_IMPORT_PARTIAL, 1);
- } else if (newKeys > 0 || updatedKeys > 0) {
- log.add(LogType.MSG_IMPORT_SUCCESS, 1);
- } else {
- log.add(LogType.MSG_IMPORT_ERROR, 1);
+ if (!cancelled) {
+ // Final log entry, it's easier to do this individually
+ if ((newKeys > 0 || updatedKeys > 0) && badKeys > 0) {
+ log.add(LogType.MSG_IMPORT_PARTIAL, 1);
+ } else if (newKeys > 0 || updatedKeys > 0) {
+ log.add(LogType.MSG_IMPORT_SUCCESS, 1);
+ } else {
+ log.add(LogType.MSG_IMPORT_ERROR, 1);
+ }
}
return new ImportKeyResult(resultType, log, newKeys, updatedKeys, badKeys, secret,
@@ -400,8 +397,7 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
return new ImportKeyResult(null,
RequiredInputParcel.createOrbotRequiredOperation(), cryptoInput);
}
- proxy = Preferences.getPreferences(mContext).getProxyPrefs().parcelableProxy
- .getProxy();
+ proxy = Preferences.getPreferences(mContext).getProxyPrefs().getProxy();
} else {
proxy = cryptoInput.getParcelableProxy().getProxy();
}
@@ -414,62 +410,61 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
}
@NonNull
- private ImportKeyResult multiThreadedKeyImport(Iterator<ParcelableKeyRing> keyListIterator,
+ private ImportKeyResult multiThreadedKeyImport(@NonNull Iterator<ParcelableKeyRing> keyListIterator,
int totKeys, final String keyServer,
final Proxy proxy) {
Log.d(Constants.TAG, "Multi-threaded key import starting");
- if (keyListIterator != null) {
- KeyImportAccumulator accumulator = new KeyImportAccumulator(totKeys, mProgressable);
-
- final ProgressScaler ignoreProgressable = new ProgressScaler();
+ KeyImportAccumulator accumulator = new KeyImportAccumulator(totKeys, mProgressable);
- final int maxThreads = 200;
- ExecutorService importExecutor = new ThreadPoolExecutor(0, maxThreads,
- 30L, TimeUnit.SECONDS,
- new SynchronousQueue<Runnable>());
+ final ProgressScaler ignoreProgressable = new ProgressScaler();
- ExecutorCompletionService<ImportKeyResult> importCompletionService =
- new ExecutorCompletionService<>(importExecutor);
+ ExecutorService importExecutor = new ThreadPoolExecutor(0, MAX_THREADS, 30L, TimeUnit.SECONDS,
+ new LinkedBlockingQueue<Runnable>());
- while (keyListIterator.hasNext()) { // submit all key rings to be imported
+ ExecutorCompletionService<ImportKeyResult> importCompletionService =
+ new ExecutorCompletionService<>(importExecutor);
- final ParcelableKeyRing pkRing = keyListIterator.next();
+ while (keyListIterator.hasNext()) { // submit all key rings to be imported
- Callable<ImportKeyResult> importOperationCallable = new Callable<ImportKeyResult>
- () {
+ final ParcelableKeyRing pkRing = keyListIterator.next();
- @Override
- public ImportKeyResult call() {
+ Callable<ImportKeyResult> importOperationCallable = new Callable<ImportKeyResult>
+ () {
- ArrayList<ParcelableKeyRing> list = new ArrayList<>();
- list.add(pkRing);
+ @Override
+ public ImportKeyResult call() {
- return serialKeyRingImport(list.iterator(), 1, keyServer,
- ignoreProgressable, proxy);
+ if (checkCancelled()) {
+ return null;
}
- };
- importCompletionService.submit(importOperationCallable);
- }
+ ArrayList<ParcelableKeyRing> list = new ArrayList<>();
+ list.add(pkRing);
- while (!accumulator.isImportFinished()) { // accumulate the results of each import
- try {
- accumulator.accumulateKeyImport(importCompletionService.take().get());
- } catch (InterruptedException | ExecutionException e) {
- Log.e(Constants.TAG, "A key could not be imported during multi-threaded " +
- "import", e);
- // do nothing?
- if (e instanceof ExecutionException) {
- // Since serialKeyRingImport does not throw any exceptions, this is what
- // would have happened if
- // we were importing the key on this thread
- throw new RuntimeException();
- }
+ return serialKeyRingImport(list.iterator(), 1, keyServer, ignoreProgressable, proxy);
+ }
+ };
+
+ importCompletionService.submit(importOperationCallable);
+ }
+
+ while (!accumulator.isImportFinished()) { // accumulate the results of each import
+ try {
+ accumulator.accumulateKeyImport(importCompletionService.take().get());
+ } catch (InterruptedException | ExecutionException e) {
+ Log.e(Constants.TAG, "A key could not be imported during multi-threaded " +
+ "import", e);
+ // do nothing?
+ if (e instanceof ExecutionException) {
+ // Since serialKeyRingImport does not throw any exceptions, this is what
+ // would have happened if
+ // we were importing the key on this thread
+ throw new RuntimeException();
}
}
- return accumulator.getConsolidatedResult();
}
- return new ImportKeyResult(ImportKeyResult.RESULT_FAIL_NOTHING, new OperationLog());
+ return accumulator.getConsolidatedResult();
+
}
/**
@@ -486,6 +481,7 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
private int mUpdatedKeys = 0;
private int mSecret = 0;
private int mResultType = 0;
+ private boolean mHasCancelledResult;
/**
* Accumulates keyring imports and updates the progressable whenever a new key is imported.
@@ -503,14 +499,25 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
}
}
- public synchronized void accumulateKeyImport(ImportKeyResult result) {
+ public void accumulateKeyImport(ImportKeyResult result) {
mImportedKeys++;
+ if (result == null) {
+ return;
+ }
+
if (mProgressable != null) {
mProgressable.setProgress(mImportedKeys, mTotalKeys);
}
- mImportLog.addAll(result.getLog().toList());//accumulates log
+ boolean notCancelledOrFirstCancelled = !result.cancelled() || !mHasCancelledResult;
+ if (notCancelledOrFirstCancelled) {
+ mImportLog.addAll(result.getLog().toList()); //accumulates log
+ if (result.cancelled()) {
+ mHasCancelledResult = true;
+ }
+ }
+
mBadKeys += result.mBadKeys;
mNewKeys += result.mNewKeys;
mUpdatedKeys += result.mUpdatedKeys;
@@ -533,7 +540,9 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
// adding required information to mResultType
// special case,no keys requested for import
- if (mBadKeys == 0 && mNewKeys == 0 && mUpdatedKeys == 0) {
+ if (mBadKeys == 0 && mNewKeys == 0 && mUpdatedKeys == 0
+ && (mResultType & ImportKeyResult.RESULT_CANCELLED)
+ != ImportKeyResult.RESULT_CANCELLED) {
mResultType = ImportKeyResult.RESULT_FAIL_NOTHING;
} else {
if (mNewKeys > 0) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java
index 9170dc139..bb8d6ad73 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java
@@ -25,9 +25,12 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
+import android.content.ClipDescription;
import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
+import android.text.TextUtils;
+import android.webkit.MimeTypeMap;
import org.apache.james.mime4j.MimeException;
import org.apache.james.mime4j.codec.DecodeMonitor;
@@ -47,7 +50,7 @@ import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
-import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
+import org.sufficientlysecure.keychain.provider.TemporaryFileProvider;
import org.sufficientlysecure.keychain.service.InputDataParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
@@ -100,32 +103,40 @@ public class InputDataOperation extends BaseOperation<InputDataParcel> {
decryptInput.setInputUri(input.getInputUri());
- currentInputUri = TemporaryStorageProvider.createFile(mContext);
+ currentInputUri = TemporaryFileProvider.createFile(mContext);
decryptInput.setOutputUri(currentInputUri);
decryptResult = op.execute(decryptInput, cryptoInput);
if (decryptResult.isPending()) {
return new InputDataResult(log, decryptResult);
}
- log.addByMerge(decryptResult, 2);
+ log.addByMerge(decryptResult, 1);
- if (!decryptResult.success()) {
- log.add(LogType.MSG_DATA_ERROR_OPENPGP, 1);
+ if ( ! decryptResult.success()) {
return new InputDataResult(InputDataResult.RESULT_ERROR, log);
}
+ // inform the storage provider about the mime type for this uri
+ if (decryptResult.getDecryptionMetadata() != null) {
+ TemporaryFileProvider.setMimeType(mContext, currentInputUri,
+ decryptResult.getDecryptionMetadata().getMimeType());
+ }
+
} else {
currentInputUri = input.getInputUri();
}
- // don't even attempt if we know the data isn't suitable for mime content
+ // don't even attempt if we know the data isn't suitable for mime content, or if we have a filename
boolean skipMimeParsing = false;
if (decryptResult != null && decryptResult.getDecryptionMetadata() != null) {
- String contentType = decryptResult.getDecryptionMetadata().getMimeType();
- if (contentType != null
- && !contentType.startsWith("multipart/")
- && !contentType.startsWith("text/")
- && !contentType.startsWith("application/")) {
+ OpenPgpMetadata metadata = decryptResult.getDecryptionMetadata();
+ String fileName = metadata.getFilename();
+ String contentType = metadata.getMimeType();
+ if (!TextUtils.isEmpty(fileName)
+ || contentType != null
+ && !contentType.startsWith("multipart/")
+ && !contentType.startsWith("text/")
+ && !"application/octet-stream".equals(contentType)) {
skipMimeParsing = true;
}
}
@@ -153,6 +164,7 @@ public class InputDataOperation extends BaseOperation<InputDataParcel> {
parser.setContentDecoding(true);
parser.setRecurse();
parser.setContentHandler(new AbstractContentHandler() {
+ private boolean mFoundHeaderWithFields = false;
private Uri uncheckedSignedDataUri;
String mFilename;
@@ -185,7 +197,7 @@ public class InputDataOperation extends BaseOperation<InputDataParcel> {
log.add(LogType.MSG_DATA_DETACHED_RAW, 3);
- uncheckedSignedDataUri = TemporaryStorageProvider.createFile(mContext, mFilename, "text/plain");
+ uncheckedSignedDataUri = TemporaryFileProvider.createFile(mContext, mFilename, "text/plain");
OutputStream out = mContext.getContentResolver().openOutputStream(uncheckedSignedDataUri, "w");
if (out == null) {
@@ -209,11 +221,19 @@ public class InputDataOperation extends BaseOperation<InputDataParcel> {
}
@Override
+ public void endHeader() throws MimeException {
+ if ( ! mFoundHeaderWithFields) {
+ parser.stop();
+ }
+ }
+
+ @Override
public void field(Field field) throws MimeException {
field = DefaultFieldParser.getParser().parse(field, DecodeMonitor.SILENT);
if (field instanceof ContentDispositionField) {
mFilename = ((ContentDispositionField) field).getFilename();
}
+ mFoundHeaderWithFields = true;
}
private void bodySignature(BodyDescriptor bd, InputStream is) throws MimeException, IOException {
@@ -282,12 +302,24 @@ public class InputDataOperation extends BaseOperation<InputDataParcel> {
log.add(LogType.MSG_DATA_MIME_PART, 2);
- log.add(LogType.MSG_DATA_MIME_TYPE, 3, bd.getMimeType());
+ String mimeType = bd.getMimeType();
+
if (mFilename != null) {
log.add(LogType.MSG_DATA_MIME_FILENAME, 3, mFilename);
+ boolean isGenericMimeType = ClipDescription.compareMimeTypes(mimeType, "application/octet-stream")
+ || ClipDescription.compareMimeTypes(mimeType, "application/x-download");
+ if (isGenericMimeType) {
+ String extension = MimeTypeMap.getFileExtensionFromUrl(mFilename);
+ String extMimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
+ if (extMimeType != null) {
+ mimeType = extMimeType;
+ log.add(LogType.MSG_DATA_MIME_FROM_EXTENSION, 3);
+ }
+ }
}
+ log.add(LogType.MSG_DATA_MIME_TYPE, 3, mimeType);
- Uri uri = TemporaryStorageProvider.createFile(mContext, mFilename, bd.getMimeType());
+ Uri uri = TemporaryFileProvider.createFile(mContext, mFilename, mimeType);
OutputStream out = mContext.getContentResolver().openOutputStream(uri, "w");
if (out == null) {
@@ -308,7 +340,7 @@ public class InputDataOperation extends BaseOperation<InputDataParcel> {
charset = "utf-8";
}
- OpenPgpMetadata metadata = new OpenPgpMetadata(mFilename, bd.getMimeType(), 0L, totalLength, charset);
+ OpenPgpMetadata metadata = new OpenPgpMetadata(mFilename, mimeType, 0L, totalLength, charset);
out.close();
outputUris.add(uri);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java
index 8f1abde83..f3ceac681 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/KeybaseVerificationOperation.java
@@ -20,39 +20,43 @@
package org.sufficientlysecure.keychain.operations;
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
-import java.net.Proxy;
-import java.util.ArrayList;
-import java.util.List;
-
import android.content.Context;
import android.support.annotation.NonNull;
+import com.textuality.keybase.lib.KeybaseQuery;
import com.textuality.keybase.lib.Proof;
import com.textuality.keybase.lib.prover.Prover;
-import de.measite.minidns.Client;
-import de.measite.minidns.DNSMessage;
-import de.measite.minidns.Question;
-import de.measite.minidns.Record;
-import de.measite.minidns.record.Data;
-import de.measite.minidns.record.TXT;
+
import org.json.JSONObject;
import org.spongycastle.openpgp.PGPUtil;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.operations.results.KeybaseVerificationResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
-import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
+import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.KeybaseVerificationParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
+import org.sufficientlysecure.keychain.util.OkHttpKeybaseClient;
import org.sufficientlysecure.keychain.util.Preferences;
import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.net.Proxy;
+import java.util.ArrayList;
+import java.util.List;
+
+import de.measite.minidns.Client;
+import de.measite.minidns.DNSMessage;
+import de.measite.minidns.Question;
+import de.measite.minidns.Record;
+import de.measite.minidns.record.Data;
+import de.measite.minidns.record.TXT;
+
public class KeybaseVerificationOperation extends BaseOperation<KeybaseVerificationParcel> {
public KeybaseVerificationOperation(Context context, ProviderHelper providerHelper,
@@ -83,6 +87,9 @@ public class KeybaseVerificationOperation extends BaseOperation<KeybaseVerificat
log.add(OperationResult.LogType.MSG_KEYBASE_VERIFICATION, 0, requiredFingerprint);
try {
+ KeybaseQuery keybaseQuery = new KeybaseQuery(new OkHttpKeybaseClient());
+ keybaseQuery.setProxy(proxy);
+
String keybaseProof = keybaseInput.mKeybaseProof;
Proof proof = new Proof(new JSONObject(keybaseProof));
mProgressable.setProgress(R.string.keybase_message_fetching_data, 0, 100);
@@ -95,7 +102,7 @@ public class KeybaseVerificationOperation extends BaseOperation<KeybaseVerificat
return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log);
}
- if (!prover.fetchProofData(proxy)) {
+ if (!prover.fetchProofData(keybaseQuery)) {
log.add(OperationResult.LogType.MSG_KEYBASE_ERROR_FETCH_PROOF, 1);
return new KeybaseVerificationResult(OperationResult.RESULT_ERROR, log);
}
@@ -144,7 +151,6 @@ public class KeybaseVerificationOperation extends BaseOperation<KeybaseVerificat
PgpDecryptVerifyOperation op = new PgpDecryptVerifyOperation(mContext, mProviderHelper, mProgressable);
PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(messageBytes)
- .setSignedLiteralData(true)
.setRequiredSignerFingerprint(requiredFingerprint);
DecryptVerifyResult decryptVerifyResult = op.execute(input, new CryptoInputParcel());
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/RevokeOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/RevokeOperation.java
index 975cf541a..3e787560a 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/RevokeOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/RevokeOperation.java
@@ -19,12 +19,13 @@
package org.sufficientlysecure.keychain.operations;
+
import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.Constants;
-import org.sufficientlysecure.keychain.operations.results.InputPendingResult;
+import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.operations.results.RevokeResult;
import org.sufficientlysecure.keychain.pgp.Progressable;
@@ -79,9 +80,8 @@ public class RevokeOperation extends BaseOperation<RevokeKeyringParcel> {
saveKeyringParcel.mRevokeSubKeys.add(masterKeyId);
- InputPendingResult revokeAndUploadResult = new EditKeyOperation(mContext,
- mProviderHelper, mProgressable, mCancelled)
- .execute(saveKeyringParcel, cryptoInputParcel);
+ EditKeyResult revokeAndUploadResult = new EditKeyOperation(mContext,
+ mProviderHelper, mProgressable, mCancelled).execute(saveKeyringParcel, cryptoInputParcel);
if (revokeAndUploadResult.isPending()) {
return revokeAndUploadResult;
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 843a55389..2ca74063c 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/SignEncryptOperation.java
@@ -17,6 +17,16 @@
package org.sufficientlysecure.keychain.operations;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicBoolean;
+
import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
@@ -40,21 +50,13 @@ import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ProgressScaler;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.FileNotFoundException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.concurrent.atomic.AtomicBoolean;
-/** This is a high-level operation, which encapsulates one or more sign/encrypt
+/**
+ * This is a high-level operation, which encapsulates one or more sign/encrypt
* operations, using URIs or byte arrays as input and output.
*
* This operation is fail-fast: If any sign/encrypt sub-operation fails or returns
* a pending result, it will terminate.
- *
*/
public class SignEncryptOperation extends BaseOperation<SignEncryptParcel> {
@@ -63,6 +65,7 @@ public class SignEncryptOperation extends BaseOperation<SignEncryptParcel> {
super(context, providerHelper, progressable, cancelled);
}
+
@NonNull
public SignEncryptResult execute(SignEncryptParcel input, CryptoInputParcel cryptoInput) {
@@ -115,7 +118,7 @@ public class SignEncryptOperation extends BaseOperation<SignEncryptParcel> {
log.add(LogType.MSG_SE_INPUT_URI, 1);
Uri uri = inputUris.removeFirst();
try {
- InputStream is = mContext.getContentResolver().openInputStream(uri);
+ InputStream is = FileHelper.openInputStreamSafe(mContext.getContentResolver(), uri);
long fileSize = FileHelper.getFileSize(mContext, uri, 0);
String filename = FileHelper.getFilename(mContext, uri);
inputData = new InputData(is, fileSize, filename);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/UploadOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/UploadOperation.java
new file mode 100644
index 000000000..e5f11eaa6
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/UploadOperation.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
+ * Copyright (C) 2015 Vincent Breitmoser <valodim@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;
+
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.Proxy;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import org.spongycastle.bcpg.ArmoredOutputStream;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
+import org.sufficientlysecure.keychain.keyimport.Keyserver.AddKeyException;
+import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
+import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
+import org.sufficientlysecure.keychain.operations.results.UploadResult;
+import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing;
+import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
+import org.sufficientlysecure.keychain.pgp.Progressable;
+import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
+import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.service.UploadKeyringParcel;
+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.Log;
+import org.sufficientlysecure.keychain.util.ParcelableProxy;
+import org.sufficientlysecure.keychain.util.Preferences;
+import org.sufficientlysecure.keychain.util.Preferences.ProxyPrefs;
+import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
+
+
+/**
+ * An operation class which implements the upload of a single key to a key server.
+ */
+public class UploadOperation extends BaseOperation<UploadKeyringParcel> {
+
+ public UploadOperation(Context context, ProviderHelper providerHelper,
+ Progressable progressable, AtomicBoolean cancelled) {
+ super(context, providerHelper, progressable, cancelled);
+ }
+
+ @NonNull
+ public UploadResult execute(UploadKeyringParcel uploadInput, CryptoInputParcel cryptoInput) {
+ OperationLog log = new OperationLog();
+
+ log.add(LogType.MSG_UPLOAD, 0);
+ updateProgress(R.string.progress_uploading, 0, 1);
+
+ Proxy proxy;
+ {
+ boolean proxyIsTor = false;
+
+ // Proxy priorities:
+ // 1. explicit proxy
+ // 2. orbot proxy state
+ // 3. proxy from preferences
+ ParcelableProxy parcelableProxy = cryptoInput.getParcelableProxy();
+ if (parcelableProxy != null) {
+ proxy = parcelableProxy.getProxy();
+ } else {
+ if ( ! OrbotHelper.isOrbotInRequiredState(mContext)) {
+ return new UploadResult(log, RequiredInputParcel.createOrbotRequiredOperation(), cryptoInput);
+ }
+ ProxyPrefs proxyPrefs = Preferences.getPreferences(mContext).getProxyPrefs();
+ if (proxyPrefs.torEnabled) {
+ proxyIsTor = true;
+ }
+ proxy = proxyPrefs.getProxy();
+ }
+
+ if (proxyIsTor) {
+ log.add(LogType.MSG_UPLOAD_PROXY_TOR, 1);
+ } else if (proxy == Proxy.NO_PROXY) {
+ log.add(LogType.MSG_UPLOAD_PROXY_DIRECT, 1);
+ } else {
+ log.add(LogType.MSG_UPLOAD_PROXY, 1, proxy.toString());
+ }
+
+ }
+
+ HkpKeyserver hkpKeyserver;
+ {
+ hkpKeyserver = new HkpKeyserver(uploadInput.mKeyserver, proxy);
+ log.add(LogType.MSG_UPLOAD_SERVER, 1, hkpKeyserver.toString());
+ }
+
+ CanonicalizedPublicKeyRing keyring = getPublicKeyringFromInput(log, uploadInput);
+ if (keyring == null) {
+ return new UploadResult(UploadResult.RESULT_ERROR, log);
+ }
+
+ return uploadKeyRingToServer(log, hkpKeyserver, keyring);
+ }
+
+ @Nullable
+ private CanonicalizedPublicKeyRing getPublicKeyringFromInput(OperationLog log, UploadKeyringParcel uploadInput) {
+
+ boolean hasMasterKeyId = uploadInput.mMasterKeyId != null;
+ boolean hasKeyringBytes = uploadInput.mUncachedKeyringBytes != null;
+ if (hasMasterKeyId == hasKeyringBytes) {
+ throw new IllegalArgumentException("either keyid xor bytes must be non-null for this method call!");
+ }
+
+ try {
+
+ if (hasMasterKeyId) {
+ log.add(LogType.MSG_UPLOAD_KEY, 0, KeyFormattingUtils.convertKeyIdToHex(uploadInput.mMasterKeyId));
+ return mProviderHelper.getCanonicalizedPublicKeyRing(uploadInput.mMasterKeyId);
+ }
+
+ CanonicalizedKeyRing canonicalizedRing =
+ UncachedKeyRing.decodeFromData(uploadInput.mUncachedKeyringBytes)
+ .canonicalize(new OperationLog(), 0, true);
+ if ( ! CanonicalizedPublicKeyRing.class.isInstance(canonicalizedRing)) {
+ throw new IllegalArgumentException("keyring bytes must contain public key ring!");
+ }
+ log.add(LogType.MSG_UPLOAD_KEY, 0, KeyFormattingUtils.convertKeyIdToHex(canonicalizedRing.getMasterKeyId()));
+ return (CanonicalizedPublicKeyRing) canonicalizedRing;
+
+ } catch (ProviderHelper.NotFoundException e) {
+ log.add(LogType.MSG_UPLOAD_ERROR_NOT_FOUND, 1);
+ return null;
+ } catch (IOException | PgpGeneralException e) {
+ log.add(LogType.MSG_UPLOAD_ERROR_IO, 1);
+ Log.e(Constants.TAG, "error uploading key", e);
+ return null;
+ }
+
+ }
+
+ @NonNull
+ private UploadResult uploadKeyRingToServer(
+ OperationLog log, HkpKeyserver server, CanonicalizedPublicKeyRing keyring) {
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ ArmoredOutputStream aos = null;
+
+ try {
+ aos = new ArmoredOutputStream(bos);
+ keyring.encode(aos);
+ aos.close();
+
+ String armoredKey = bos.toString("UTF-8");
+ server.add(armoredKey);
+
+ updateProgress(R.string.progress_uploading, 1, 1);
+
+ log.add(LogType.MSG_UPLOAD_SUCCESS, 1);
+ return new UploadResult(UploadResult.RESULT_OK, log);
+ } catch (IOException e) {
+ Log.e(Constants.TAG, "IOException", e);
+
+ log.add(LogType.MSG_UPLOAD_ERROR_IO, 1);
+ return new UploadResult(UploadResult.RESULT_ERROR, log);
+ } catch (AddKeyException e) {
+ Log.e(Constants.TAG, "AddKeyException", e);
+
+ log.add(LogType.MSG_UPLOAD_ERROR_UPLOAD, 1);
+ return new UploadResult(UploadResult.RESULT_ERROR, log);
+ } finally {
+ try {
+ if (aos != null) {
+ aos.close();
+ }
+ bos.close();
+ } catch (IOException e) {
+ // this is just a finally thing, no matter if it doesn't work out.
+ }
+ }
+ }
+
+} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/BenchmarkResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/BenchmarkResult.java
new file mode 100644
index 000000000..473ae9886
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/BenchmarkResult.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.operations.results;
+
+import android.os.Parcel;
+
+
+public class BenchmarkResult extends OperationResult {
+
+ public BenchmarkResult(int result, OperationLog log) {
+ super(result, log);
+ }
+
+ /** Construct from a parcel - trivial because we have no extra data. */
+ public BenchmarkResult(Parcel source) {
+ super(source);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ }
+
+ public static Creator<BenchmarkResult> CREATOR = new Creator<BenchmarkResult>() {
+ public BenchmarkResult createFromParcel(final Parcel source) {
+ return new BenchmarkResult(source);
+ }
+
+ public BenchmarkResult[] newArray(final int size) {
+ return new BenchmarkResult[size];
+ }
+ };
+
+}
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 95cf179af..f19ba5250 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
@@ -39,6 +39,8 @@ public class DecryptVerifyResult extends InputPendingResult {
byte[] mOutputBytes;
+ public long mOperationTime;
+
public DecryptVerifyResult(int result, OperationLog log) {
super(result, log);
}
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 6098d59d5..fa383a7b5 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
@@ -38,6 +38,11 @@ public class EditKeyResult extends InputPendingResult {
mMasterKeyId = null;
}
+ public EditKeyResult(OperationLog log, InputPendingResult result) {
+ super(log, result);
+ mMasterKeyId = null;
+ }
+
public EditKeyResult(Parcel source) {
super(source);
mMasterKeyId = source.readInt() != 0 ? source.readLong() : null;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ExportResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ExportResult.java
index e21ef949f..135f5af3d 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ExportResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/ExportResult.java
@@ -24,39 +24,18 @@ import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
public class ExportResult extends InputPendingResult {
- final int mOkPublic, mOkSecret;
-
public ExportResult(int result, OperationLog log) {
- this(result, log, 0, 0);
- }
-
- public ExportResult(int result, OperationLog log, int okPublic, int okSecret) {
super(result, log);
- mOkPublic = okPublic;
- mOkSecret = okSecret;
- }
-
-
- public ExportResult(OperationLog log, RequiredInputParcel requiredInputParcel,
- CryptoInputParcel cryptoInputParcel) {
- super(log, requiredInputParcel, cryptoInputParcel);
- // we won't use these values
- mOkPublic = -1;
- mOkSecret = -1;
}
/** Construct from a parcel - trivial because we have no extra data. */
public ExportResult(Parcel source) {
super(source);
- mOkPublic = source.readInt();
- mOkSecret = source.readInt();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
- dest.writeInt(mOkPublic);
- dest.writeInt(mOkSecret);
}
public static Creator<ExportResult> CREATOR = new Creator<ExportResult>() {
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
index 0a8c1f653..ed6674ef7 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java
@@ -19,6 +19,7 @@
package org.sufficientlysecure.keychain.operations.results;
import android.os.Parcel;
+import android.support.annotation.NonNull;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
@@ -32,13 +33,13 @@ public class InputPendingResult extends OperationResult {
// in case operation needs to add to/changes the cryptoInputParcel sent to it
public final CryptoInputParcel mCryptoInputParcel;
- public InputPendingResult(int result, OperationLog log) {
+ public InputPendingResult(int result, @NonNull OperationLog log) {
super(result, log);
mRequiredInput = null;
mCryptoInputParcel = null;
}
- public InputPendingResult(OperationLog log, InputPendingResult result) {
+ public InputPendingResult(@NonNull OperationLog log, @NonNull InputPendingResult result) {
super(RESULT_PENDING, log);
if (!result.isPending()) {
throw new AssertionError("sub result must be pending!");
@@ -47,7 +48,7 @@ public class InputPendingResult extends OperationResult {
mCryptoInputParcel = result.mCryptoInputParcel;
}
- public InputPendingResult(OperationLog log, RequiredInputParcel requiredInput,
+ public InputPendingResult(@NonNull OperationLog log, RequiredInputParcel requiredInput,
CryptoInputParcel cryptoInputParcel) {
super(RESULT_PENDING, log);
mRequiredInput = requiredInput;
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 a03658808..9877f2318 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
@@ -474,6 +474,7 @@ public abstract class OperationResult implements Parcelable {
MSG_KC_UID_BAD (LogLevel.WARN, R.string.msg_kc_uid_bad),
MSG_KC_UID_CERT_DUP (LogLevel.DEBUG, R.string.msg_kc_uid_cert_dup),
MSG_KC_UID_DUP (LogLevel.DEBUG, R.string.msg_kc_uid_dup),
+ MSG_KC_UID_TOO_MANY (LogLevel.DEBUG, R.string.msg_kc_uid_too_many),
MSG_KC_UID_FOREIGN (LogLevel.DEBUG, R.string.msg_kc_uid_foreign),
MSG_KC_UID_NO_CERT (LogLevel.DEBUG, R.string.msg_kc_uid_no_cert),
MSG_KC_UID_REVOKE_DUP (LogLevel.DEBUG, R.string.msg_kc_uid_revoke_dup),
@@ -513,7 +514,7 @@ public abstract class OperationResult implements Parcelable {
MSG_CR_ERROR_NO_USER_ID (LogLevel.ERROR, R.string.msg_cr_error_no_user_id),
MSG_CR_ERROR_NO_CERTIFY (LogLevel.ERROR, R.string.msg_cr_error_no_certify),
MSG_CR_ERROR_NULL_EXPIRY(LogLevel.ERROR, R.string.msg_cr_error_null_expiry),
- MSG_CR_ERROR_KEYSIZE_512 (LogLevel.ERROR, R.string.msg_cr_error_keysize_512),
+ MSG_CR_ERROR_KEYSIZE_2048(LogLevel.ERROR, R.string.msg_cr_error_keysize_2048),
MSG_CR_ERROR_NO_KEYSIZE (LogLevel.ERROR, R.string.msg_cr_error_no_keysize),
MSG_CR_ERROR_NO_CURVE (LogLevel.ERROR, R.string.msg_cr_error_no_curve),
MSG_CR_ERROR_UNKNOWN_ALGO (LogLevel.ERROR, R.string.msg_cr_error_unknown_algo),
@@ -634,10 +635,13 @@ public abstract class OperationResult implements Parcelable {
MSG_EK_ERROR_NOT_FOUND (LogLevel.ERROR, R.string.msg_ek_error_not_found),
// decryptverify
+ MSG_DC_ASKIP_BAD_FLAGS (LogLevel.DEBUG, R.string.msg_dc_askip_bad_flags),
+ MSG_DC_ASKIP_UNAVAILABLE (LogLevel.DEBUG, R.string.msg_dc_askip_unavailable),
MSG_DC_ASKIP_NO_KEY (LogLevel.DEBUG, R.string.msg_dc_askip_no_key),
MSG_DC_ASKIP_NOT_ALLOWED (LogLevel.DEBUG, R.string.msg_dc_askip_not_allowed),
MSG_DC_ASYM (LogLevel.DEBUG, R.string.msg_dc_asym),
MSG_DC_CHARSET (LogLevel.DEBUG, R.string.msg_dc_charset),
+ MSG_DC_BACKUP_VERSION (LogLevel.DEBUG, R.string.msg_dc_backup_version),
MSG_DC_CLEAR_DATA (LogLevel.DEBUG, R.string.msg_dc_clear_data),
MSG_DC_CLEAR_DECOMPRESS (LogLevel.DEBUG, R.string.msg_dc_clear_decompress),
MSG_DC_CLEAR_META_FILE (LogLevel.DEBUG, R.string.msg_dc_clear_meta_file),
@@ -660,6 +664,7 @@ public abstract class OperationResult implements Parcelable {
MSG_DC_ERROR_INPUT (LogLevel.ERROR, R.string.msg_dc_error_input),
MSG_DC_ERROR_NO_DATA (LogLevel.ERROR, R.string.msg_dc_error_no_data),
MSG_DC_ERROR_NO_KEY (LogLevel.ERROR, R.string.msg_dc_error_no_key),
+ MSG_DC_ERROR_NO_SIGNATURE (LogLevel.ERROR, R.string.msg_dc_error_no_signature),
MSG_DC_ERROR_PGP_EXCEPTION (LogLevel.ERROR, R.string.msg_dc_error_pgp_exception),
MSG_DC_INTEGRITY_CHECK_OK (LogLevel.INFO, R.string.msg_dc_integrity_check_ok),
MSG_DC_OK_META_ONLY (LogLevel.OK, R.string.msg_dc_ok_meta_only),
@@ -686,6 +691,7 @@ public abstract class OperationResult implements Parcelable {
MSG_VL_ERROR_MISSING_SIGLIST (LogLevel.ERROR, R.string.msg_vl_error_no_siglist),
MSG_VL_ERROR_MISSING_LITERAL (LogLevel.ERROR, R.string.msg_vl_error_missing_literal),
MSG_VL_ERROR_MISSING_KEY (LogLevel.ERROR, R.string.msg_vl_error_wrong_key),
+ MSG_VL_ERROR_NO_SIGNATURE (LogLevel.ERROR, R.string.msg_vl_error_no_signature),
MSG_VL_CLEAR_SIGNATURE_CHECK (LogLevel.DEBUG, R.string.msg_vl_clear_signature_check),
MSG_VL_ERROR_INTEGRITY_CHECK (LogLevel.ERROR, R.string.msg_vl_error_integrity_check),
MSG_VL_OK (LogLevel.OK, R.string.msg_vl_ok),
@@ -702,7 +708,6 @@ public abstract class OperationResult implements Parcelable {
// pgpsignencrypt
MSG_PSE_ASYMMETRIC (LogLevel.INFO, R.string.msg_pse_asymmetric),
- MSG_PSE_CLEARSIGN_ONLY (LogLevel.DEBUG, R.string.msg_pse_clearsign_only),
MSG_PSE_COMPRESSING (LogLevel.DEBUG, R.string.msg_pse_compressing),
MSG_PSE_ENCRYPTING (LogLevel.DEBUG, R.string.msg_pse_encrypting),
MSG_PSE_ERROR_BAD_PASSPHRASE (LogLevel.ERROR, R.string.msg_pse_error_bad_passphrase),
@@ -762,23 +767,25 @@ public abstract class OperationResult implements Parcelable {
MSG_IMPORT_PARTIAL (LogLevel.ERROR, R.string.msg_import_partial),
MSG_IMPORT_SUCCESS (LogLevel.OK, R.string.msg_import_success),
- MSG_EXPORT (LogLevel.START, R.plurals.msg_export),
- MSG_EXPORT_FILE_NAME (LogLevel.INFO, R.string.msg_export_file_name),
- MSG_EXPORT_UPLOAD_PUBLIC (LogLevel.START, R.string.msg_export_upload_public),
- MSG_EXPORT_PUBLIC (LogLevel.DEBUG, R.string.msg_export_public),
- MSG_EXPORT_SECRET (LogLevel.DEBUG, R.string.msg_export_secret),
- MSG_EXPORT_ALL (LogLevel.START, R.string.msg_export_all),
- MSG_EXPORT_ERROR_NO_FILE (LogLevel.ERROR, R.string.msg_export_error_no_file),
- MSG_EXPORT_ERROR_FOPEN (LogLevel.ERROR, R.string.msg_export_error_fopen),
- MSG_EXPORT_ERROR_NO_URI (LogLevel.ERROR, R.string.msg_export_error_no_uri),
- MSG_EXPORT_ERROR_URI_OPEN (LogLevel.ERROR, R.string.msg_export_error_uri_open),
- MSG_EXPORT_ERROR_STORAGE (LogLevel.ERROR, R.string.msg_export_error_storage),
- MSG_EXPORT_ERROR_DB (LogLevel.ERROR, R.string.msg_export_error_db),
- MSG_EXPORT_ERROR_IO (LogLevel.ERROR, R.string.msg_export_error_io),
- MSG_EXPORT_ERROR_KEY (LogLevel.ERROR, R.string.msg_export_error_key),
- MSG_EXPORT_ERROR_UPLOAD (LogLevel.ERROR, R.string.msg_export_error_upload),
- MSG_EXPORT_SUCCESS (LogLevel.OK, R.string.msg_export_success),
- MSG_EXPORT_UPLOAD_SUCCESS (LogLevel.OK, R.string.msg_export_upload_success),
+ MSG_BACKUP(LogLevel.START, R.plurals.msg_backup),
+ MSG_BACKUP_PUBLIC(LogLevel.DEBUG, R.string.msg_backup_public),
+ MSG_BACKUP_SECRET(LogLevel.DEBUG, R.string.msg_backup_secret),
+ MSG_BACKUP_ALL(LogLevel.START, R.string.msg_backup_all),
+ MSG_BACKUP_ERROR_URI_OPEN(LogLevel.ERROR, R.string.msg_backup_error_uri_open),
+ MSG_BACKUP_ERROR_DB(LogLevel.ERROR, R.string.msg_backup_error_db),
+ MSG_BACKUP_ERROR_IO(LogLevel.ERROR, R.string.msg_backup_error_io),
+ MSG_BACKUP_SUCCESS(LogLevel.OK, R.string.msg_backup_success),
+
+ MSG_UPLOAD(LogLevel.START, R.string.msg_upload),
+ MSG_UPLOAD_KEY(LogLevel.INFO, R.string.msg_upload_key),
+ MSG_UPLOAD_PROXY_DIRECT(LogLevel.DEBUG, R.string.msg_upload_proxy_direct),
+ MSG_UPLOAD_PROXY_TOR(LogLevel.DEBUG, R.string.msg_upload_proxy_tor),
+ MSG_UPLOAD_PROXY(LogLevel.DEBUG, R.string.msg_upload_proxy),
+ MSG_UPLOAD_SERVER(LogLevel.DEBUG, R.string.msg_upload_server),
+ MSG_UPLOAD_SUCCESS(LogLevel.OK, R.string.msg_upload_success),
+ MSG_UPLOAD_ERROR_NOT_FOUND(LogLevel.ERROR, R.string.msg_upload_error_not_found),
+ MSG_UPLOAD_ERROR_IO(LogLevel.ERROR, R.string.msg_upload_error_key),
+ MSG_UPLOAD_ERROR_UPLOAD(LogLevel.ERROR, R.string.msg_upload_error_upload),
MSG_CRT_UPLOAD_SUCCESS (LogLevel.OK, R.string.msg_crt_upload_success),
@@ -829,7 +836,6 @@ public abstract class OperationResult implements Parcelable {
MSG_DATA (LogLevel.START, R.string.msg_data),
MSG_DATA_OPENPGP (LogLevel.DEBUG, R.string.msg_data_openpgp),
MSG_DATA_ERROR_IO (LogLevel.ERROR, R.string.msg_data_error_io),
- MSG_DATA_ERROR_OPENPGP (LogLevel.ERROR, R.string.msg_data_error_openpgp),
MSG_DATA_DETACHED (LogLevel.INFO, R.string.msg_data_detached),
MSG_DATA_DETACHED_CLEAR (LogLevel.WARN, R.string.msg_data_detached_clear),
MSG_DATA_DETACHED_SIG (LogLevel.DEBUG, R.string.msg_data_detached_sig),
@@ -838,6 +844,7 @@ public abstract class OperationResult implements Parcelable {
MSG_DATA_DETACHED_TRAILING (LogLevel.WARN, R.string.msg_data_detached_trailing),
MSG_DATA_DETACHED_UNSUPPORTED (LogLevel.WARN, R.string.msg_data_detached_unsupported),
MSG_DATA_MIME_BAD(LogLevel.INFO, R.string.msg_data_mime_bad),
+ MSG_DATA_MIME_FROM_EXTENSION (LogLevel.DEBUG, R.string.msg_data_mime_from_extension),
MSG_DATA_MIME_FILENAME (LogLevel.DEBUG, R.string.msg_data_mime_filename),
MSG_DATA_MIME_LENGTH (LogLevel.DEBUG, R.string.msg_data_mime_length),
MSG_DATA_MIME (LogLevel.DEBUG, R.string.msg_data_mime),
@@ -868,6 +875,16 @@ public abstract class OperationResult implements Parcelable {
MSG_LV_FETCH_ERROR_IO (LogLevel.ERROR, R.string.msg_lv_fetch_error_io),
MSG_LV_FETCH_ERROR_FORMAT(LogLevel.ERROR, R.string.msg_lv_fetch_error_format),
MSG_LV_FETCH_ERROR_NOTHING (LogLevel.ERROR, R.string.msg_lv_fetch_error_nothing),
+
+ MSG_BENCH (LogLevel.START, R.string.msg_bench),
+ MSG_BENCH_ENC_TIME (LogLevel.DEBUG, R.string.msg_bench_enc_time),
+ MSG_BENCH_ENC_TIME_AVG (LogLevel.INFO, R.string.msg_bench_enc_time_avg),
+ MSG_BENCH_DEC_TIME (LogLevel.DEBUG, R.string.msg_bench_dec_time),
+ MSG_BENCH_DEC_TIME_AVG (LogLevel.INFO, R.string.msg_bench_enc_time_avg),
+ MSG_BENCH_S2K_FOR_IT (LogLevel.DEBUG, R.string.msg_bench_s2k_for_it),
+ MSG_BENCH_S2K_100MS_ITS (LogLevel.INFO, R.string.msg_bench_s2k_100ms_its),
+ MSG_BENCH_SUCCESS (LogLevel.OK, R.string.msg_bench_success),
+
;
public final int mMsgId;
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 2b33b8ace..12b091e32 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
@@ -26,6 +26,7 @@ import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
public class PgpSignEncryptResult extends InputPendingResult {
byte[] mDetachedSignature;
+ public long mOperationTime;
public void setDetachedSignature(byte[] detachedSignature) {
mDetachedSignature = detachedSignature;
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 0e0c5d598..60f47be3c 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
@@ -56,6 +56,10 @@ public class SignEncryptResult extends InputPendingResult {
return mResultBytes;
}
+ public ArrayList<PgpSignEncryptResult> getResults() {
+ return mResults;
+ }
+
public int describeContents() {
return 0;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/UploadResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/UploadResult.java
new file mode 100644
index 000000000..ea2b373a9
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/UploadResult.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.operations.results;
+
+import android.os.Parcel;
+import android.support.annotation.NonNull;
+
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
+
+
+public class UploadResult extends InputPendingResult {
+
+ final int mOkPublic, mOkSecret;
+
+ public UploadResult(int result, OperationLog log) {
+ this(result, log, 0, 0);
+ }
+
+ public UploadResult(int result, OperationLog log, int okPublic, int okSecret) {
+ super(result, log);
+ mOkPublic = okPublic;
+ mOkSecret = okSecret;
+ }
+
+
+ public UploadResult(@NonNull OperationLog log, RequiredInputParcel requiredInputParcel,
+ CryptoInputParcel cryptoInputParcel) {
+ super(log, requiredInputParcel, cryptoInputParcel);
+ // we won't use these values
+ mOkPublic = -1;
+ mOkSecret = -1;
+ }
+
+ /** Construct from a parcel - trivial because we have no extra data. */
+ public UploadResult(Parcel source) {
+ super(source);
+ mOkPublic = source.readInt();
+ mOkSecret = source.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(mOkPublic);
+ dest.writeInt(mOkSecret);
+ }
+
+ public static Creator<UploadResult> CREATOR = new Creator<UploadResult>() {
+ public UploadResult createFromParcel(final Parcel source) {
+ return new UploadResult(source);
+ }
+
+ public UploadResult[] newArray(final int size) {
+ return new UploadResult[size];
+ }
+ };
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java
index 18a27dd96..6f1e78ce6 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java
@@ -154,8 +154,13 @@ public abstract class CanonicalizedKeyRing extends KeyRing {
return getRing().getEncoded();
}
- public boolean containsSubkey(String expectedFingerprint) {
+ /// Returns true iff the keyring contains a primary key or mutually bound subkey with the expected fingerprint
+ public boolean containsBoundSubkey(String expectedFingerprint) {
for (CanonicalizedPublicKey key : publicKeyIterator()) {
+ boolean isMasterOrMutuallyBound = key.isMasterKey() || key.canSign();
+ if (!isMasterOrMutuallyBound) {
+ continue;
+ }
if (KeyFormattingUtils.convertFingerprintToHex(
key.getFingerprint()).equalsIgnoreCase(expectedFingerprint)) {
return true;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java
index 412468a48..476b4e59c 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKey.java
@@ -44,13 +44,17 @@ import java.util.Iterator;
public class CanonicalizedPublicKey extends UncachedPublicKey {
// this is the parent key ring
- final KeyRing mRing;
+ final CanonicalizedKeyRing mRing;
- CanonicalizedPublicKey(KeyRing ring, PGPPublicKey key) {
+ CanonicalizedPublicKey(CanonicalizedKeyRing ring, PGPPublicKey key) {
super(key);
mRing = ring;
}
+ public CanonicalizedKeyRing getKeyRing() {
+ return mRing;
+ }
+
public IterableIterator<String> getUserIds() {
return new IterableIterator<String>(mPublicKey.getUserIDs());
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java
index 9d059b58f..2dd1e2dde 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java
@@ -91,11 +91,12 @@ public class OpenPgpSignatureResultBuilder {
return mInsecure;
}
- public void initValid(CanonicalizedPublicKeyRing signingRing,
- CanonicalizedPublicKey signingKey) {
+ public void initValid(CanonicalizedPublicKey signingKey) {
setSignatureAvailable(true);
setKnownKey(true);
+ CanonicalizedKeyRing signingRing = signingKey.getKeyRing();
+
// from RING
setKeyId(signingRing.getMasterKeyId());
try {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java
index 3eef7759c..bc9b54cd6 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java
@@ -18,7 +18,7 @@
package org.sufficientlysecure.keychain.pgp;
-import java.io.InputStream;
+
import java.util.HashSet;
import android.net.Uri;
@@ -36,7 +36,6 @@ public class PgpDecryptVerifyInputParcel implements Parcelable {
private boolean mDecryptMetadataOnly;
private byte[] mDetachedSignature;
private String mRequiredSignerFingerprint;
- private boolean mSignedLiteralData;
public PgpDecryptVerifyInputParcel() {
}
@@ -61,7 +60,6 @@ public class PgpDecryptVerifyInputParcel implements Parcelable {
mDecryptMetadataOnly = source.readInt() != 0;
mDetachedSignature = source.createByteArray();
mRequiredSignerFingerprint = source.readString();
- mSignedLiteralData = source.readInt() != 0;
}
@Override
@@ -80,7 +78,6 @@ public class PgpDecryptVerifyInputParcel implements Parcelable {
dest.writeInt(mDecryptMetadataOnly ? 1 : 0);
dest.writeByteArray(mDetachedSignature);
dest.writeString(mRequiredSignerFingerprint);
- dest.writeInt(mSignedLiteralData ? 1 : 0);
}
byte[] getInputBytes() {
@@ -150,15 +147,6 @@ public class PgpDecryptVerifyInputParcel implements Parcelable {
return this;
}
- boolean isSignedLiteralData() {
- return mSignedLiteralData;
- }
-
- public PgpDecryptVerifyInputParcel setSignedLiteralData(boolean signedLiteralData) {
- mSignedLiteralData = signedLiteralData;
- return this;
- }
-
public static final Creator<PgpDecryptVerifyInputParcel> CREATOR = new Creator<PgpDecryptVerifyInputParcel>() {
public PgpDecryptVerifyInputParcel createFromParcel(final Parcel source) {
return new PgpDecryptVerifyInputParcel(source);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java
index 007f686e8..ea7465209 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java
@@ -18,13 +18,24 @@
package org.sufficientlysecure.keychain.pgp;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.SignatureException;
+import java.util.Date;
+import java.util.Iterator;
+
import android.content.Context;
import android.support.annotation.NonNull;
+import android.text.TextUtils;
import android.webkit.MimeTypeMap;
import org.openintents.openpgp.OpenPgpDecryptionResult;
import org.openintents.openpgp.OpenPgpMetadata;
-import org.openintents.openpgp.OpenPgpSignatureResult;
import org.spongycastle.bcpg.ArmoredInputStream;
import org.spongycastle.openpgp.PGPCompressedData;
import org.spongycastle.openpgp.PGPDataValidationException;
@@ -33,18 +44,14 @@ import org.spongycastle.openpgp.PGPEncryptedDataList;
import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPKeyValidationException;
import org.spongycastle.openpgp.PGPLiteralData;
-import org.spongycastle.openpgp.PGPOnePassSignature;
-import org.spongycastle.openpgp.PGPOnePassSignatureList;
import org.spongycastle.openpgp.PGPPBEEncryptedData;
import org.spongycastle.openpgp.PGPPublicKeyEncryptedData;
-import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureList;
import org.spongycastle.openpgp.PGPUtil;
-import org.spongycastle.openpgp.jcajce.JcaPGPObjectFactory;
+import org.spongycastle.openpgp.jcajce.JcaSkipMarkerPGPObjectFactory;
import org.spongycastle.openpgp.operator.PBEDataDecryptorFactory;
import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider;
import org.spongycastle.openpgp.operator.jcajce.CachingDataDecryptorFactory;
-import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder;
import org.spongycastle.util.encoders.DecoderException;
@@ -52,13 +59,13 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Constants.key;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.BaseOperation;
+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.CanonicalizedSecretKey.SecretKeyType;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
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.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
@@ -68,17 +75,6 @@ import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.ProgressScaler;
-import java.io.BufferedInputStream;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.security.SignatureException;
-import java.util.Date;
-import java.util.Iterator;
-
public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInputParcel> {
public PgpDecryptVerifyOperation(Context context, ProviderHelper providerHelper, Progressable progressable) {
@@ -91,6 +87,8 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
InputData inputData;
OutputStream outputStream;
+ long startTime = System.currentTimeMillis();
+
if (input.getInputBytes() != null) {
byte[] inputBytes = input.getInputBytes();
inputData = new InputData(new ByteArrayInputStream(inputBytes), inputBytes.length);
@@ -126,6 +124,8 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
result.setOutputBytes(outputData);
}
+ result.mOperationTime = System.currentTimeMillis() - startTime;
+ Log.d(Constants.TAG, "total time taken: " + String.format("%.2f", result.mOperationTime / 1000.0) + "s");
return result;
}
@@ -153,9 +153,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
// it is ascii armored
Log.d(Constants.TAG, "ASCII Armor Header Line: " + aIn.getArmorHeaderLine());
- if (input.isSignedLiteralData()) {
- return verifySignedLiteralData(input, aIn, outputStream, 0);
- } else if (aIn.isClearText()) {
+ if (aIn.isClearText()) {
// a cleartext signature, verify it with the other method
return verifyCleartextSignature(aIn, outputStream, 0);
} else {
@@ -186,133 +184,6 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
}
}
- /**Verify signed plaintext data (PGP/INLINE). */
- @NonNull
- private DecryptVerifyResult verifySignedLiteralData(
- PgpDecryptVerifyInputParcel input, InputStream in, OutputStream out, int indent)
- throws IOException, PGPException {
- OperationLog log = new OperationLog();
- log.add(LogType.MSG_VL, indent);
-
- // thinking that the proof-fetching operation is going to take most of the time
- updateProgress(R.string.progress_reading_data, 75, 100);
-
- JcaPGPObjectFactory pgpF = new JcaPGPObjectFactory(in);
- Object o = pgpF.nextObject();
- if (o instanceof PGPCompressedData) {
- log.add(LogType.MSG_DC_CLEAR_DECOMPRESS, indent + 1);
-
- pgpF = new JcaPGPObjectFactory(((PGPCompressedData) o).getDataStream());
- o = pgpF.nextObject();
- updateProgress(R.string.progress_decompressing_data, 80, 100);
- }
-
- // all we want to see is a OnePassSignatureList followed by LiteralData
- if (!(o instanceof PGPOnePassSignatureList)) {
- log.add(LogType.MSG_VL_ERROR_MISSING_SIGLIST, indent);
- return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
- }
- PGPOnePassSignatureList sigList = (PGPOnePassSignatureList) o;
-
- // go through all signatures (should be just one), make sure we have
- // the key and it matches the one we’re looking for
- CanonicalizedPublicKeyRing signingRing = null;
- CanonicalizedPublicKey signingKey = null;
- int signatureIndex = -1;
- for (int i = 0; i < sigList.size(); ++i) {
- try {
- long sigKeyId = sigList.get(i).getKeyID();
- signingRing = mProviderHelper.getCanonicalizedPublicKeyRing(
- KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(sigKeyId)
- );
- signingKey = signingRing.getPublicKey(sigKeyId);
- signatureIndex = i;
- } catch (ProviderHelper.NotFoundException e) {
- Log.d(Constants.TAG, "key not found, trying next signature...");
- }
- }
-
- // there has to be a key, and it has to be the right one
- if (signingKey == null) {
- log.add(LogType.MSG_VL_ERROR_MISSING_KEY, indent);
- Log.d(Constants.TAG, "Failed to find key in signed-literal message");
- return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
- }
-
- String fingerprint = KeyFormattingUtils.convertFingerprintToHex(signingRing.getFingerprint());
- if (!(input.getRequiredSignerFingerprint().equals(fingerprint))) {
- log.add(LogType.MSG_VL_ERROR_MISSING_KEY, indent);
- Log.d(Constants.TAG, "Fingerprint mismatch; wanted " + input.getRequiredSignerFingerprint() +
- " got " + fingerprint + "!");
- return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
- }
-
- OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder();
-
- PGPOnePassSignature signature = sigList.get(signatureIndex);
- signatureResultBuilder.initValid(signingRing, signingKey);
-
- JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider =
- new JcaPGPContentVerifierBuilderProvider()
- .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
- signature.init(contentVerifierBuilderProvider, signingKey.getPublicKey());
-
- o = pgpF.nextObject();
-
- if (!(o instanceof PGPLiteralData)) {
- log.add(LogType.MSG_VL_ERROR_MISSING_LITERAL, indent);
- return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
- }
-
- PGPLiteralData literalData = (PGPLiteralData) o;
-
- log.add(LogType.MSG_DC_CLEAR_DATA, indent + 1);
- updateProgress(R.string.progress_decrypting, 85, 100);
-
- InputStream dataIn = literalData.getInputStream();
-
- int length;
- byte[] buffer = new byte[1 << 16];
- while ((length = dataIn.read(buffer)) > 0) {
- out.write(buffer, 0, length);
- signature.update(buffer, 0, length);
- }
-
- updateProgress(R.string.progress_verifying_signature, 95, 100);
- log.add(LogType.MSG_VL_CLEAR_SIGNATURE_CHECK, indent + 1);
-
- PGPSignatureList signatureList = (PGPSignatureList) pgpF.nextObject();
- PGPSignature messageSignature = signatureList.get(signatureIndex);
-
- // Verify signature and check binding signatures
- boolean validSignature = signature.verify(messageSignature);
- if (validSignature) {
- log.add(LogType.MSG_DC_CLEAR_SIGNATURE_OK, indent + 1);
- } else {
- log.add(LogType.MSG_DC_CLEAR_SIGNATURE_BAD, indent + 1);
- }
- signatureResultBuilder.setValidSignature(validSignature);
-
- OpenPgpSignatureResult signatureResult = signatureResultBuilder.build();
-
- if (signatureResult.getResult() != OpenPgpSignatureResult.RESULT_VALID_CONFIRMED
- && signatureResult.getResult() != OpenPgpSignatureResult.RESULT_VALID_UNCONFIRMED) {
- log.add(LogType.MSG_VL_ERROR_INTEGRITY_CHECK, indent);
- return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
- }
-
- updateProgress(R.string.progress_done, 100, 100);
-
- log.add(LogType.MSG_VL_OK, indent);
-
- // Return a positive result, with metadata and verification info
- DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
- result.setSignatureResult(signatureResult);
- result.setDecryptionResult(
- new OpenPgpDecryptionResult(OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED));
- return result;
- }
-
private static class EncryptStreamResult {
// this is non-null iff an error occured, return directly
@@ -335,6 +206,57 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
}
+ private static class ArmorHeaders {
+ String charset = null;
+ Integer backupVersion = null;
+ }
+
+ private ArmorHeaders parseArmorHeaders(InputStream in, OperationLog log, int indent) {
+ ArmorHeaders armorHeaders = new ArmorHeaders();
+
+ // If the input stream is armored, and there is a charset specified, take a note for later
+ // https://tools.ietf.org/html/rfc4880#page56
+ if (in instanceof ArmoredInputStream) {
+ ArmoredInputStream aIn = (ArmoredInputStream) in;
+ if (aIn.getArmorHeaders() != null) {
+ for (String header : aIn.getArmorHeaders()) {
+ String[] pieces = header.split(":", 2);
+ if (pieces.length != 2
+ || TextUtils.isEmpty(pieces[0])
+ || TextUtils.isEmpty(pieces[1])) {
+ continue;
+ }
+
+ switch (pieces[0].toLowerCase()) {
+ case "charset": {
+ armorHeaders.charset = pieces[1].trim();
+ break;
+ }
+ case "backupversion": {
+ try {
+ armorHeaders.backupVersion = Integer.valueOf(pieces[1].trim());
+ } catch (NumberFormatException e) {
+ continue;
+ }
+ break;
+ }
+ default: {
+ // continue;
+ }
+ }
+ }
+ if (armorHeaders.charset != null) {
+ log.add(LogType.MSG_DC_CHARSET, indent, armorHeaders.charset);
+ }
+ if (armorHeaders.backupVersion != null) {
+ log.add(LogType.MSG_DC_BACKUP_VERSION, indent, Integer.toString(armorHeaders.backupVersion));
+ }
+ }
+ }
+
+ return armorHeaders;
+ }
+
/** Decrypt and/or verify binary or ascii armored pgp data. */
@NonNull
private DecryptVerifyResult decryptVerify(
@@ -349,38 +271,27 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
int currentProgress = 0;
updateProgress(R.string.progress_reading_data, currentProgress, 100);
- // If the input stream is armored, and there is a charset specified, take a note for later
- // https://tools.ietf.org/html/rfc4880#page56
- String charset = null;
- if (in instanceof ArmoredInputStream) {
- ArmoredInputStream aIn = (ArmoredInputStream) in;
- if (aIn.getArmorHeaders() != null) {
- for (String header : aIn.getArmorHeaders()) {
- String[] pieces = header.split(":", 2);
- if (pieces.length == 2 && "charset".equalsIgnoreCase(pieces[0])) {
- charset = pieces[1].trim();
- break;
- }
- }
- if (charset != null) {
- log.add(LogType.MSG_DC_CHARSET, indent, charset);
- }
- }
+ // parse ASCII Armor headers
+ ArmorHeaders armorHeaders = parseArmorHeaders(in, log, indent);
+ String charset = armorHeaders.charset;
+ boolean useBackupCode = false;
+ if (armorHeaders.backupVersion != null && armorHeaders.backupVersion == 1) {
+ useBackupCode = true;
}
- OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder();
OpenPgpDecryptionResultBuilder decryptionResultBuilder = new OpenPgpDecryptionResultBuilder();
- JcaPGPObjectFactory plainFact;
+ JcaSkipMarkerPGPObjectFactory plainFact;
Object dataChunk;
EncryptStreamResult esResult = null;
{ // resolve encrypted (symmetric and asymmetric) packets
- JcaPGPObjectFactory pgpF = new JcaPGPObjectFactory(in);
+ JcaSkipMarkerPGPObjectFactory pgpF = new JcaSkipMarkerPGPObjectFactory(in);
Object obj = pgpF.nextObject();
if (obj instanceof PGPEncryptedDataList) {
esResult = handleEncryptedPacket(
- input, cryptoInput, (PGPEncryptedDataList) obj, log, indent, currentProgress);
+ input, cryptoInput, (PGPEncryptedDataList) obj, log, indent,
+ currentProgress, useBackupCode);
// if there is an error, nothing left to do here
if (esResult.errorResult != null) {
@@ -401,7 +312,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
decryptionResultBuilder.setInsecure(true);
}
- plainFact = new JcaPGPObjectFactory(esResult.cleartextStream);
+ plainFact = new JcaSkipMarkerPGPObjectFactory(esResult.cleartextStream);
dataChunk = plainFact.nextObject();
} else {
@@ -415,10 +326,6 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
log.add(LogType.MSG_DC_PREP_STREAMS, indent);
- int signatureIndex = -1;
- CanonicalizedPublicKeyRing signingRing = null;
- CanonicalizedPublicKey signingKey = null;
-
log.add(LogType.MSG_DC_CLEAR, indent);
indent += 1;
@@ -430,63 +337,13 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
PGPCompressedData compressedData = (PGPCompressedData) dataChunk;
- JcaPGPObjectFactory fact = new JcaPGPObjectFactory(compressedData.getDataStream());
+ JcaSkipMarkerPGPObjectFactory fact = new JcaSkipMarkerPGPObjectFactory(compressedData.getDataStream());
dataChunk = fact.nextObject();
plainFact = fact;
}
- // resolve leading signature data
- PGPOnePassSignature signature = null;
- if (dataChunk instanceof PGPOnePassSignatureList) {
- log.add(LogType.MSG_DC_CLEAR_SIGNATURE, indent + 1);
- currentProgress += 2;
- updateProgress(R.string.progress_processing_signature, currentProgress, 100);
-
- PGPOnePassSignatureList sigList = (PGPOnePassSignatureList) dataChunk;
-
- // NOTE: following code is similar to processSignature, but for PGPOnePassSignature
-
- // go through all signatures
- // and find out for which signature we have a key in our database
- for (int i = 0; i < sigList.size(); ++i) {
- try {
- long sigKeyId = sigList.get(i).getKeyID();
- signingRing = mProviderHelper.getCanonicalizedPublicKeyRing(
- KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(sigKeyId)
- );
- signingKey = signingRing.getPublicKey(sigKeyId);
- signatureIndex = i;
- } catch (ProviderHelper.NotFoundException e) {
- Log.d(Constants.TAG, "key not found, trying next signature...");
- }
- }
-
- if (signingKey != null) {
- // key found in our database!
- signature = sigList.get(signatureIndex);
-
- signatureResultBuilder.initValid(signingRing, signingKey);
-
- JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider =
- new JcaPGPContentVerifierBuilderProvider()
- .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
- signature.init(contentVerifierBuilderProvider, signingKey.getPublicKey());
- } else {
- // no key in our database -> return "unknown pub key" status including the first key id
- if (!sigList.isEmpty()) {
- signatureResultBuilder.setSignatureAvailable(true);
- signatureResultBuilder.setKnownKey(false);
- signatureResultBuilder.setKeyId(sigList.get(0).getKeyID());
- }
- }
-
- // check for insecure signing key
- // TODO: checks on signingRing ?
- if (signingKey != null && ! PgpSecurityConstants.isSecureKey(signingKey)) {
- log.add(LogType.MSG_DC_INSECURE_KEY, indent + 1);
- signatureResultBuilder.setInsecure(true);
- }
-
+ PgpSignatureChecker signatureChecker = new PgpSignatureChecker(mProviderHelper);
+ if (signatureChecker.initializeOnePassSignature(dataChunk, log, indent +1)) {
dataChunk = plainFact.nextObject();
}
@@ -512,8 +369,9 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
PGPLiteralData literalData = (PGPLiteralData) dataChunk;
String originalFilename = literalData.getFileName();
+ // reject filenames with slashes completely (path traversal issue)
if (originalFilename.contains("/")) {
- originalFilename = originalFilename.substring(originalFilename.lastIndexOf('/'));
+ originalFilename = "";
}
String mimeType = null;
if (literalData.getFormat() == PGPLiteralData.TEXT
@@ -534,14 +392,14 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
if (!"".equals(originalFilename)) {
log.add(LogType.MSG_DC_CLEAR_META_FILE, indent + 1, originalFilename);
}
- log.add(LogType.MSG_DC_CLEAR_META_MIME, indent + 1,
- mimeType);
log.add(LogType.MSG_DC_CLEAR_META_TIME, indent + 1,
new Date(literalData.getModificationTime().getTime()).toString());
// return here if we want to decrypt the metadata only
if (input.isDecryptMetadataOnly()) {
+ log.add(LogType.MSG_DC_CLEAR_META_MIME, indent + 1, mimeType);
+
// this operation skips the entire stream to find the data length!
Long originalSize = literalData.findDataLength();
@@ -566,23 +424,18 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
return result;
}
- int endProgress;
- if (signature != null) {
- endProgress = 90;
- } else if (esResult != null && esResult.encryptedData.isIntegrityProtected()) {
- endProgress = 95;
- } else {
- endProgress = 100;
- }
ProgressScaler progressScaler =
- new ProgressScaler(mProgressable, currentProgress, endProgress, 100);
+ new ProgressScaler(mProgressable, currentProgress, 95, 100);
InputStream dataIn = literalData.getInputStream();
+ long opTime, startTime = System.currentTimeMillis();
+
long alreadyWritten = 0;
long wholeSize = 0; // TODO inputData.getSize() - inputData.getStreamPosition();
int length;
- byte[] buffer = new byte[1 << 16];
+ byte[] buffer = new byte[8192];
+ byte[] firstBytes = new byte[48];
while ((length = dataIn.read(buffer)) > 0) {
// Log.d(Constants.TAG, "read bytes: " + length);
if (out != null) {
@@ -590,11 +443,15 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
}
// update signature buffer if signature is also present
- if (signature != null) {
- signature.update(buffer, 0, length);
+ signatureChecker.updateSignatureData(buffer, 0, length);
+
+ // note down first couple of bytes for "magic bytes" file type detection
+ if (alreadyWritten == 0) {
+ System.arraycopy(buffer, 0, firstBytes, 0, length > firstBytes.length ? firstBytes.length : length);
}
alreadyWritten += length;
+ // noinspection ConstantConditions, TODO progress
if (wholeSize > 0) {
long progress = 100 * alreadyWritten / wholeSize;
// stop at 100% for wrong file sizes...
@@ -603,36 +460,36 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
}
progressScaler.setProgress((int) progress, 100);
}
- // TODO: slow annealing to fake a progress?
}
- metadata = new OpenPgpMetadata(
- originalFilename, mimeType, literalData.getModificationTime().getTime(), alreadyWritten, charset);
+ if (signatureChecker.isInitialized()) {
- if (signature != null) {
- updateProgress(R.string.progress_verifying_signature, 90, 100);
- log.add(LogType.MSG_DC_CLEAR_SIGNATURE_CHECK, indent);
+ Object o = plainFact.nextObject();
+ boolean signatureCheckOk = signatureChecker.verifySignatureOnePass(o, log, indent + 1);
- PGPSignatureList signatureList = (PGPSignatureList) plainFact.nextObject();
- PGPSignature messageSignature = signatureList.get(signatureIndex);
-
- // Verify signature
- boolean validSignature = signature.verify(messageSignature);
- if (validSignature) {
- log.add(LogType.MSG_DC_CLEAR_SIGNATURE_OK, indent + 1);
- } else {
- log.add(LogType.MSG_DC_CLEAR_SIGNATURE_BAD, indent + 1);
+ if (!signatureCheckOk) {
+ return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
}
- // check for insecure hash algorithms
- if (!PgpSecurityConstants.isSecureHashAlgorithm(signature.getHashAlgorithm())) {
- log.add(LogType.MSG_DC_INSECURE_HASH_ALGO, indent + 1);
- signatureResultBuilder.setInsecure(true);
- }
+ }
+
+ opTime = System.currentTimeMillis()-startTime;
+ Log.d(Constants.TAG, "decrypt time taken: " + String.format("%.2f", opTime / 1000.0) + "s");
- signatureResultBuilder.setValidSignature(validSignature);
+ // special treatment to detect pgp mime types
+ if (matchesPrefix(firstBytes, "-----BEGIN PGP PUBLIC KEY BLOCK-----")
+ || matchesPrefix(firstBytes, "-----BEGIN PGP PRIVATE KEY BLOCK-----")) {
+ mimeType = Constants.MIME_TYPE_KEYS;
+ } else if (matchesPrefix(firstBytes, "-----BEGIN PGP MESSAGE-----")) {
+ // this is NOT application/pgp-encrypted, see RFC 3156!
+ mimeType = Constants.MIME_TYPE_ENCRYPTED_ALTERNATE;
}
+ log.add(LogType.MSG_DC_CLEAR_META_MIME, indent + 1, mimeType);
+
+ metadata = new OpenPgpMetadata(
+ originalFilename, mimeType, literalData.getModificationTime().getTime(), alreadyWritten, charset);
+
indent -= 1;
if (esResult != null) {
@@ -645,7 +502,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
log.add(LogType.MSG_DC_ERROR_INTEGRITY_CHECK, indent);
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
}
- } else if (signature == null) {
+ } else if ( ! signatureChecker.isInitialized() ) {
// If no signature is present, we *require* an MDC!
// Handle missing integrity protection like failed integrity protection!
// The MDC packet can be stripped by an attacker!
@@ -662,23 +519,17 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
result.setCachedCryptoInputParcel(cryptoInput);
- result.setSignatureResult(signatureResultBuilder.build());
+ result.setSignatureResult(signatureChecker.getSignatureResult());
result.setDecryptionResult(decryptionResultBuilder.build());
result.setDecryptionMetadata(metadata);
+ result.mOperationTime = opTime;
return result;
}
private EncryptStreamResult handleEncryptedPacket(PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput,
- PGPEncryptedDataList enc, OperationLog log, int indent, int currentProgress) throws PGPException {
-
- // TODO is this necessary?
- /*
- else if (obj instanceof PGPEncryptedDataList) {
- enc = (PGPEncryptedDataList) pgpF.nextObject();
- }
- */
+ PGPEncryptedDataList enc, OperationLog log, int indent, int currentProgress, boolean useBackupCode) throws PGPException {
EncryptStreamResult result = new EncryptStreamResult();
@@ -720,11 +571,6 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent + 1);
continue;
}
- if (secretKeyRing == null) {
- // continue with the next packet in the while loop
- log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent + 1);
- continue;
- }
// allow only specific keys for decryption?
if (input.getAllowedKeyIds() != null) {
@@ -744,9 +590,16 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
// get subkey which has been used for this encryption packet
secretEncryptionKey = secretKeyRing.getSecretKey(subKeyId);
- if (secretEncryptionKey == null) {
- // should actually never happen, so no need to be more specific.
- log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent + 1);
+
+ if (!secretEncryptionKey.canEncrypt()) {
+ secretEncryptionKey = null;
+ log.add(LogType.MSG_DC_ASKIP_BAD_FLAGS, indent + 1);
+ continue;
+ }
+
+ if (!secretEncryptionKey.getSecretKeyType().isUsable()) {
+ secretEncryptionKey = null;
+ log.add(LogType.MSG_DC_ASKIP_UNAVAILABLE, indent + 1);
continue;
}
@@ -820,8 +673,11 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
if (passphrase == null) {
log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1);
+ RequiredInputParcel requiredInputParcel = useBackupCode ?
+ RequiredInputParcel.createRequiredBackupCode() :
+ RequiredInputParcel.createRequiredSymmetricPassphrase();
return result.with(new DecryptVerifyResult(log,
- RequiredInputParcel.createRequiredSymmetricPassphrase(),
+ requiredInputParcel,
cryptoInput));
}
@@ -864,8 +720,10 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
result.cleartextStream = encryptedDataSymmetric.getDataStream(decryptorFactory);
} catch (PGPDataValidationException e) {
log.add(LogType.MSG_DC_ERROR_SYM_PASSPHRASE, indent + 1);
- return result.with(new DecryptVerifyResult(log,
- RequiredInputParcel.createRequiredSymmetricPassphrase(), cryptoInput));
+ RequiredInputParcel requiredInputParcel = useBackupCode ?
+ RequiredInputParcel.createRequiredBackupCode() :
+ RequiredInputParcel.createRequiredSymmetricPassphrase();
+ return result.with(new DecryptVerifyResult(log, requiredInputParcel, cryptoInput));
}
result.encryptedData = encryptedDataSymmetric;
@@ -950,83 +808,53 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
OperationLog log = new OperationLog();
- OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder();
-
- ByteArrayOutputStream out = new ByteArrayOutputStream();
+ byte[] clearText;
+ { // read cleartext
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
- updateProgress(R.string.progress_reading_data, 0, 100);
+ updateProgress(R.string.progress_reading_data, 0, 100);
- ByteArrayOutputStream lineOut = new ByteArrayOutputStream();
- int lookAhead = readInputLine(lineOut, aIn);
- byte[] lineSep = getLineSeparator();
+ ByteArrayOutputStream lineOut = new ByteArrayOutputStream();
+ int lookAhead = readInputLine(lineOut, aIn);
+ byte[] lineSep = getLineSeparator();
- byte[] line = lineOut.toByteArray();
- out.write(line, 0, getLengthWithoutSeparator(line));
- out.write(lineSep);
-
- while (lookAhead != -1 && aIn.isClearText()) {
- lookAhead = readInputLine(lineOut, lookAhead, aIn);
- line = lineOut.toByteArray();
+ byte[] line = lineOut.toByteArray();
out.write(line, 0, getLengthWithoutSeparator(line));
out.write(lineSep);
- }
- out.close();
+ while (lookAhead != -1 && aIn.isClearText()) {
+ lookAhead = readInputLine(lineOut, lookAhead, aIn);
+ line = lineOut.toByteArray();
+ out.write(line, 0, getLengthWithoutSeparator(line));
+ out.write(lineSep);
+ }
+
+ out.close();
+ clearText = out.toByteArray();
+ }
- byte[] clearText = out.toByteArray();
if (outputStream != null) {
outputStream.write(clearText);
outputStream.close();
}
updateProgress(R.string.progress_processing_signature, 60, 100);
- JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(aIn);
+ JcaSkipMarkerPGPObjectFactory pgpFact = new JcaSkipMarkerPGPObjectFactory(aIn);
+
+ PgpSignatureChecker signatureChecker = new PgpSignatureChecker(mProviderHelper);
- PGPSignatureList sigList = (PGPSignatureList) pgpFact.nextObject();
- if (sigList == null) {
+ Object o = pgpFact.nextObject();
+ if (!signatureChecker.initializeSignature(o, log, indent+1)) {
log.add(LogType.MSG_DC_ERROR_INVALID_DATA, 0);
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
}
- PGPSignature signature = processPGPSignatureList(sigList, signatureResultBuilder, log, indent);
-
- if (signature != null) {
+ if (signatureChecker.isInitialized()) {
try {
updateProgress(R.string.progress_verifying_signature, 90, 100);
- log.add(LogType.MSG_DC_CLEAR_SIGNATURE_CHECK, indent);
-
- InputStream sigIn = new BufferedInputStream(new ByteArrayInputStream(clearText));
-
- lookAhead = readInputLine(lineOut, sigIn);
-
- processLine(signature, lineOut.toByteArray());
- if (lookAhead != -1) {
- do {
- lookAhead = readInputLine(lineOut, lookAhead, sigIn);
-
- signature.update((byte) '\r');
- signature.update((byte) '\n');
-
- processLine(signature, lineOut.toByteArray());
- } while (lookAhead != -1);
- }
-
- // Verify signature and check binding signatures
- boolean validSignature = signature.verify();
- if (validSignature) {
- log.add(LogType.MSG_DC_CLEAR_SIGNATURE_OK, indent + 1);
- } else {
- log.add(LogType.MSG_DC_CLEAR_SIGNATURE_BAD, indent + 1);
- }
-
- // check for insecure hash algorithms
- if (!PgpSecurityConstants.isSecureHashAlgorithm(signature.getHashAlgorithm())) {
- log.add(LogType.MSG_DC_INSECURE_HASH_ALGO, indent + 1);
- signatureResultBuilder.setInsecure(true);
- }
-
- signatureResultBuilder.setValidSignature(validSignature);
+ signatureChecker.updateSignatureWithCleartext(clearText);
+ signatureChecker.verifySignature(log, indent);
} catch (SignatureException e) {
Log.d(Constants.TAG, "SignatureException", e);
@@ -1045,7 +873,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
clearText.length);
DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
- result.setSignatureResult(signatureResultBuilder.build());
+ result.setSignatureResult(signatureChecker.getSignatureResult());
result.setDecryptionResult(
new OpenPgpDecryptionResult(OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED));
result.setDecryptionMetadata(metadata);
@@ -1059,30 +887,28 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
OperationLog log = new OperationLog();
- OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder();
-
updateProgress(R.string.progress_processing_signature, 0, 100);
InputStream detachedSigIn = new ByteArrayInputStream(input.getDetachedSignature());
detachedSigIn = PGPUtil.getDecoderStream(detachedSigIn);
- JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(detachedSigIn);
+ JcaSkipMarkerPGPObjectFactory pgpFact = new JcaSkipMarkerPGPObjectFactory(detachedSigIn);
- PGPSignatureList sigList;
Object o = pgpFact.nextObject();
if (o instanceof PGPCompressedData) {
PGPCompressedData c1 = (PGPCompressedData) o;
- pgpFact = new JcaPGPObjectFactory(c1.getDataStream());
- sigList = (PGPSignatureList) pgpFact.nextObject();
- } else if (o instanceof PGPSignatureList) {
- sigList = (PGPSignatureList) o;
- } else {
+ pgpFact = new JcaSkipMarkerPGPObjectFactory(c1.getDataStream());
+ o = pgpFact.nextObject();
+ }
+
+ PgpSignatureChecker signatureChecker = new PgpSignatureChecker(mProviderHelper);
+
+ if ( ! signatureChecker.initializeSignature(o, log, indent+1)) {
log.add(LogType.MSG_DC_ERROR_INVALID_DATA, 0);
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
}
- PGPSignature signature = processPGPSignatureList(sigList, signatureResultBuilder, log, indent);
+ if (signatureChecker.isInitialized()) {
- if (signature != null) {
updateProgress(R.string.progress_reading_data, 60, 100);
ProgressScaler progressScaler = new ProgressScaler(mProgressable, 60, 90, 100);
@@ -1097,7 +923,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
}
// update signature buffer if signature is also present
- signature.update(buffer, 0, length);
+ signatureChecker.updateSignatureData(buffer, 0, length);
alreadyWritten += length;
if (wholeSize > 0) {
@@ -1108,105 +934,28 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
}
progressScaler.setProgress((int) progress, 100);
}
- // TODO: slow annealing to fake a progress?
}
updateProgress(R.string.progress_verifying_signature, 90, 100);
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_CHECK, indent);
- // Verify signature and check binding signatures
- boolean validSignature = signature.verify();
- if (validSignature) {
- log.add(LogType.MSG_DC_CLEAR_SIGNATURE_OK, indent + 1);
- } else {
- log.add(LogType.MSG_DC_CLEAR_SIGNATURE_BAD, indent + 1);
- }
-
- // check for insecure hash algorithms
- if (!PgpSecurityConstants.isSecureHashAlgorithm(signature.getHashAlgorithm())) {
- log.add(LogType.MSG_DC_INSECURE_HASH_ALGO, indent + 1);
- signatureResultBuilder.setInsecure(true);
- }
+ signatureChecker.verifySignature(log, indent);
- signatureResultBuilder.setValidSignature(validSignature);
}
updateProgress(R.string.progress_done, 100, 100);
log.add(LogType.MSG_DC_OK, indent);
+ // TODO return metadata object?
+
DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
- result.setSignatureResult(signatureResultBuilder.build());
+ result.setSignatureResult(signatureChecker.getSignatureResult());
result.setDecryptionResult(
new OpenPgpDecryptionResult(OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED));
return result;
}
- private PGPSignature processPGPSignatureList(
- PGPSignatureList sigList, OpenPgpSignatureResultBuilder signatureResultBuilder,
- OperationLog log, int indent)
- throws PGPException {
- CanonicalizedPublicKeyRing signingRing = null;
- CanonicalizedPublicKey signingKey = null;
- int signatureIndex = -1;
-
- // go through all signatures
- // and find out for which signature we have a key in our database
- for (int i = 0; i < sigList.size(); ++i) {
- try {
- long sigKeyId = sigList.get(i).getKeyID();
- signingRing = mProviderHelper.getCanonicalizedPublicKeyRing(
- KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(sigKeyId)
- );
- signingKey = signingRing.getPublicKey(sigKeyId);
- signatureIndex = i;
- } catch (ProviderHelper.NotFoundException e) {
- Log.d(Constants.TAG, "key not found, trying next signature...");
- }
- }
-
- PGPSignature signature = null;
-
- if (signingKey != null) {
- // key found in our database!
- signature = sigList.get(signatureIndex);
-
- signatureResultBuilder.initValid(signingRing, signingKey);
-
- JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider =
- new JcaPGPContentVerifierBuilderProvider()
- .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
- signature.init(contentVerifierBuilderProvider, signingKey.getPublicKey());
- } else {
- // no key in our database -> return "unknown pub key" status including the first key id
- if (!sigList.isEmpty()) {
- signatureResultBuilder.setSignatureAvailable(true);
- signatureResultBuilder.setKnownKey(false);
- signatureResultBuilder.setKeyId(sigList.get(0).getKeyID());
- }
- }
-
- // check for insecure signing key
- // TODO: checks on signingRing ?
- if (signingKey != null && ! PgpSecurityConstants.isSecureKey(signingKey)) {
- log.add(LogType.MSG_DC_INSECURE_KEY, indent + 1);
- signatureResultBuilder.setInsecure(true);
- }
-
- return signature;
- }
-
- /**
- * Mostly taken from ClearSignedFileProcessor in Bouncy Castle
- */
- private static void processLine(PGPSignature sig, byte[] line)
- throws SignatureException {
- int length = getLengthWithoutWhiteSpace(line);
- if (length > 0) {
- sig.update(line, 0, length);
- }
- }
-
private static int readInputLine(ByteArrayOutputStream bOut, InputStream fIn)
throws IOException {
bOut.reset();
@@ -1272,22 +1021,21 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
return b == '\r' || b == '\n';
}
- private static int getLengthWithoutWhiteSpace(byte[] line) {
- int end = line.length - 1;
-
- while (end >= 0 && isWhiteSpace(line[end])) {
- end--;
- }
-
- return end + 1;
- }
-
- private static boolean isWhiteSpace(byte b) {
- return b == '\r' || b == '\n' || b == '\t' || b == ' ';
- }
-
private static byte[] getLineSeparator() {
String nl = System.getProperty("line.separator");
return nl.getBytes();
}
+
+ /// Convenience method - Trivially checks if a byte array matches the bytes of a plain text string
+ // Assumes data.length >= needle.length()
+ static boolean matchesPrefix(byte[] data, String needle) {
+ byte[] needleBytes = needle.getBytes();
+ for (int i = 0; i < needle.length(); i++) {
+ if (data[i] != needleBytes[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java
index e8d1d3111..016651c3b 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java
@@ -18,23 +18,15 @@
package org.sufficientlysecure.keychain.pgp;
-import android.content.Context;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
import android.support.annotation.NonNull;
import android.text.TextUtils;
import org.sufficientlysecure.keychain.Constants;
-import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.util.Log;
-import org.sufficientlysecure.keychain.util.Preferences;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.security.SecureRandom;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
public class PgpHelper {
@@ -51,35 +43,6 @@ public class PgpHelper {
Pattern.DOTALL);
/**
- * Deletes file securely by overwriting it with random data before deleting it.
- * <p/>
- * TODO: Does this really help on flash storage?
- *
- * @throws IOException
- */
- public static void deleteFileSecurely(Context context, Progressable progressable, File file)
- throws IOException {
- long length = file.length();
- SecureRandom random = new SecureRandom();
- RandomAccessFile raf = new RandomAccessFile(file, "rws");
- raf.seek(0);
- raf.getFilePointer();
- byte[] data = new byte[1 << 16];
- int pos = 0;
- String msg = context.getString(R.string.progress_deleting_securely, file.getName());
- while (pos < length) {
- if (progressable != null) {
- progressable.setProgress(msg, (int) (100 * pos / length), 100);
- }
- random.nextBytes(data);
- raf.write(data);
- pos += data.length;
- }
- raf.close();
- file.delete();
- }
-
- /**
* Fixing broken PGP MESSAGE Strings coming from GMail/AOSP Mail
*/
public static String fixPgpMessage(String message) {
@@ -117,7 +80,7 @@ public class PgpHelper {
}
}
- public static String getPgpContent(@NonNull CharSequence input) {
+ public static String getPgpMessageContent(@NonNull CharSequence input) {
Log.dEscaped(Constants.TAG, "input: " + input);
Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(input);
@@ -141,4 +104,18 @@ public class PgpHelper {
}
}
+ public static String getPgpKeyContent(@NonNull CharSequence input) {
+ Log.dEscaped(Constants.TAG, "input: " + input);
+
+ Matcher matcher = PgpHelper.PGP_PUBLIC_KEY.matcher(input);
+ if (matcher.matches()) {
+ String text = matcher.group(1);
+ text = fixPgpMessage(text);
+
+ Log.dEscaped(Constants.TAG, "input fixed: " + text);
+ return text;
+ }
+ return null;
+ }
+
}
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 6f156c201..59b840054 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java
@@ -171,8 +171,8 @@ public class PgpKeyOperation {
log.add(LogType.MSG_CR_ERROR_NO_KEYSIZE, indent);
return null;
}
- if (add.mKeySize < 512) {
- log.add(LogType.MSG_CR_ERROR_KEYSIZE_512, indent);
+ if (add.mKeySize < 2048) {
+ log.add(LogType.MSG_CR_ERROR_KEYSIZE_2048, indent);
return null;
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSecurityConstants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSecurityConstants.java
index cbd8ce47a..7ad7b4d0f 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSecurityConstants.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSecurityConstants.java
@@ -79,8 +79,8 @@ public class PgpSecurityConstants {
*/
private static HashSet<Integer> sHashAlgorithmsWhitelist = new HashSet<>(Arrays.asList(
// MD5: broken
- // SHA1: broken
- // RIPEMD160: same security properties as SHA1
+ HashAlgorithmTags.SHA1, // TODO: disable when SHA256 is widely deployed
+ HashAlgorithmTags.RIPEMD160, // same security properties as SHA1, TODO: disable when SHA256 is widely deployed
// DOUBLE_SHA: not used widely
// MD2: not used widely
// TIGER_192: not used widely
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java
index 36d1a07cb..c2c6234eb 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java
@@ -44,6 +44,7 @@ public class PgpSignEncryptInputParcel implements Parcelable {
protected boolean mDetachedSignature = false;
protected boolean mHiddenRecipients = false;
protected boolean mIntegrityProtected = true;
+ protected boolean mAddBackupHeader = false;
public PgpSignEncryptInputParcel() {
@@ -70,6 +71,7 @@ public class PgpSignEncryptInputParcel implements Parcelable {
mDetachedSignature = source.readInt() == 1;
mHiddenRecipients = source.readInt() == 1;
mIntegrityProtected = source.readInt() == 1;
+ mAddBackupHeader = source.readInt() == 1;
}
@Override
@@ -100,6 +102,7 @@ public class PgpSignEncryptInputParcel implements Parcelable {
dest.writeInt(mDetachedSignature ? 1 : 0);
dest.writeInt(mHiddenRecipients ? 1 : 0);
dest.writeInt(mIntegrityProtected ? 1 : 0);
+ dest.writeInt(mAddBackupHeader ? 1 : 0);
}
public String getCharset() {
@@ -244,6 +247,15 @@ public class PgpSignEncryptInputParcel implements Parcelable {
return this;
}
+ public PgpSignEncryptInputParcel setAddBackupHeader(boolean addBackupHeader) {
+ this.mAddBackupHeader = addBackupHeader;
+ return this;
+ }
+
+ public boolean isAddBackupHeader() {
+ return mAddBackupHeader;
+ }
+
public boolean isHiddenRecipients() {
return mHiddenRecipients;
}
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 29b2ef727..45641b33a 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java
@@ -53,6 +53,7 @@ import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.ProgressScaler;
+import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
@@ -148,6 +149,10 @@ public class PgpSignEncryptOperation extends BaseOperation {
if (input.getCharset() != null) {
armorOut.setHeader("Charset", input.getCharset());
}
+ // add proprietary header to indicate that this is a key backup
+ if (input.isAddBackupHeader()) {
+ armorOut.setHeader("BackupVersion", "1");
+ }
out = armorOut;
} else {
out = outputStream;
@@ -316,6 +321,8 @@ public class PgpSignEncryptOperation extends BaseOperation {
ArmoredOutputStream detachedArmorOut = null;
BCPGOutputStream detachedBcpgOut = null;
+ long opTime, startTime = System.currentTimeMillis();
+
try {
if (enableEncryption) {
@@ -361,7 +368,7 @@ public class PgpSignEncryptOperation extends BaseOperation {
long alreadyWritten = 0;
int length;
byte[] buffer = new byte[1 << 16];
- InputStream in = inputData.getInputStream();
+ InputStream in = new BufferedInputStream(inputData.getInputStream());
while ((length = in.read(buffer)) > 0) {
pOut.write(buffer, 0, length);
@@ -389,7 +396,7 @@ public class PgpSignEncryptOperation extends BaseOperation {
// write -----BEGIN PGP SIGNED MESSAGE-----
armorOut.beginClearText(input.getSignatureHashAlgorithm());
- InputStream in = inputData.getInputStream();
+ InputStream in = new BufferedInputStream(inputData.getInputStream());
final BufferedReader reader = new BufferedReader(new InputStreamReader(in));
// update signature buffer with first line
@@ -421,7 +428,7 @@ public class PgpSignEncryptOperation extends BaseOperation {
updateProgress(R.string.progress_signing, 8, 100);
log.add(LogType.MSG_PSE_SIGNING_DETACHED, indent);
- InputStream in = inputData.getInputStream();
+ InputStream in = new BufferedInputStream(inputData.getInputStream());
// handle output stream separately for detached signatures
detachedByteOut = new ByteArrayOutputStream();
@@ -458,7 +465,7 @@ public class PgpSignEncryptOperation extends BaseOperation {
updateProgress(R.string.progress_signing, 8, 100);
log.add(LogType.MSG_PSE_SIGNING, indent);
- InputStream in = inputData.getInputStream();
+ InputStream in = new BufferedInputStream(inputData.getInputStream());
if (enableCompression) {
compressGen = new PGPCompressedDataGenerator(input.getCompressionAlgorithm());
@@ -491,9 +498,7 @@ public class PgpSignEncryptOperation extends BaseOperation {
literalGen.close();
} else {
- pOut = null;
- // TODO: Is this log right?
- log.add(LogType.MSG_PSE_CLEARSIGN_ONLY, indent);
+ throw new AssertionError("cannot clearsign in non-ascii armored text, this is a bug!");
}
if (enableSignature) {
@@ -513,6 +518,10 @@ public class PgpSignEncryptOperation extends BaseOperation {
}
}
+ opTime = System.currentTimeMillis() -startTime;
+ Log.d(Constants.TAG, "sign/encrypt time taken: " + String.format("%.2f",
+ opTime / 1000.0) + "s");
+
// closing outputs
// NOTE: closing needs to be done in the correct order!
if (encryptionOut != null) {
@@ -556,6 +565,7 @@ public class PgpSignEncryptOperation extends BaseOperation {
log.add(LogType.MSG_PSE_OK, indent);
PgpSignEncryptResult result = new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_OK, log);
+ result.mOperationTime = opTime;
if (detachedByteOut != null) {
try {
detachedByteOut.flush();
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignatureChecker.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignatureChecker.java
new file mode 100644
index 000000000..ed5566bc1
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignatureChecker.java
@@ -0,0 +1,344 @@
+package org.sufficientlysecure.keychain.pgp;
+
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.SignatureException;
+
+import org.openintents.openpgp.OpenPgpSignatureResult;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPOnePassSignature;
+import org.spongycastle.openpgp.PGPOnePassSignatureList;
+import org.spongycastle.openpgp.PGPSignature;
+import org.spongycastle.openpgp.PGPSignatureList;
+import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
+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.provider.KeychainContract.KeyRings;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.util.Log;
+
+
+/** This class is used to track the state of a single signature verification.
+ *
+ *
+ */
+class PgpSignatureChecker {
+
+ OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder();
+
+ private CanonicalizedPublicKey signingKey;
+
+ private int signatureIndex;
+ PGPOnePassSignature onePassSignature;
+ PGPSignature signature;
+
+ ProviderHelper mProviderHelper;
+
+ PgpSignatureChecker(ProviderHelper providerHelper) {
+ mProviderHelper = providerHelper;
+ }
+
+ boolean initializeSignature(Object dataChunk, OperationLog log, int indent) throws PGPException {
+
+ if (!(dataChunk instanceof PGPSignatureList)) {
+ return false;
+ }
+
+ PGPSignatureList sigList = (PGPSignatureList) dataChunk;
+ findAvailableSignature(sigList);
+
+ if (signingKey != null) {
+
+ // key found in our database!
+ signatureResultBuilder.initValid(signingKey);
+
+ JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider =
+ new JcaPGPContentVerifierBuilderProvider()
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+ signature.init(contentVerifierBuilderProvider, signingKey.getPublicKey());
+ checkKeySecurity(log, indent);
+
+
+ } else if (!sigList.isEmpty()) {
+
+ signatureResultBuilder.setSignatureAvailable(true);
+ signatureResultBuilder.setKnownKey(false);
+ signatureResultBuilder.setKeyId(sigList.get(0).getKeyID());
+
+ }
+
+ return true;
+
+ }
+
+ boolean initializeOnePassSignature(Object dataChunk, OperationLog log, int indent) throws PGPException {
+
+ if (!(dataChunk instanceof PGPOnePassSignatureList)) {
+ return false;
+ }
+
+ log.add(LogType.MSG_DC_CLEAR_SIGNATURE, indent + 1);
+
+ PGPOnePassSignatureList sigList = (PGPOnePassSignatureList) dataChunk;
+ findAvailableSignature(sigList);
+
+ if (signingKey != null) {
+
+ // key found in our database!
+ signatureResultBuilder.initValid(signingKey);
+
+ JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider =
+ new JcaPGPContentVerifierBuilderProvider()
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
+ onePassSignature.init(contentVerifierBuilderProvider, signingKey.getPublicKey());
+
+ checkKeySecurity(log, indent);
+
+ } else if (!sigList.isEmpty()) {
+
+ signatureResultBuilder.setSignatureAvailable(true);
+ signatureResultBuilder.setKnownKey(false);
+ signatureResultBuilder.setKeyId(sigList.get(0).getKeyID());
+
+ }
+
+ return true;
+
+ }
+
+ private void checkKeySecurity(OperationLog log, int indent) {
+ // TODO: checks on signingRing ?
+ if (!PgpSecurityConstants.isSecureKey(signingKey)) {
+ log.add(LogType.MSG_DC_INSECURE_KEY, indent + 1);
+ signatureResultBuilder.setInsecure(true);
+ }
+ }
+
+ public boolean isInitialized() {
+ return signingKey != null;
+ }
+
+ private void findAvailableSignature(PGPOnePassSignatureList sigList) {
+ // go through all signatures (should be just one), make sure we have
+ // the key and it matches the one we’re looking for
+ for (int i = 0; i < sigList.size(); ++i) {
+ try {
+ long sigKeyId = sigList.get(i).getKeyID();
+ CanonicalizedPublicKeyRing signingRing = mProviderHelper.getCanonicalizedPublicKeyRing(
+ KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(sigKeyId)
+ );
+ CanonicalizedPublicKey keyCandidate = signingRing.getPublicKey(sigKeyId);
+ if ( ! keyCandidate.canSign()) {
+ continue;
+ }
+ signatureIndex = i;
+ signingKey = keyCandidate;
+ onePassSignature = sigList.get(i);
+ return;
+ } catch (ProviderHelper.NotFoundException e) {
+ Log.d(Constants.TAG, "key not found, trying next signature...");
+ }
+ }
+ }
+
+ public void findAvailableSignature(PGPSignatureList sigList) {
+ // go through all signatures (should be just one), make sure we have
+ // the key and it matches the one we’re looking for
+ for (int i = 0; i < sigList.size(); ++i) {
+ try {
+ long sigKeyId = sigList.get(i).getKeyID();
+ CanonicalizedPublicKeyRing signingRing = mProviderHelper.getCanonicalizedPublicKeyRing(
+ KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(sigKeyId)
+ );
+ CanonicalizedPublicKey keyCandidate = signingRing.getPublicKey(sigKeyId);
+ if ( ! keyCandidate.canSign()) {
+ continue;
+ }
+ signatureIndex = i;
+ signingKey = keyCandidate;
+ signature = sigList.get(i);
+ return;
+ } catch (ProviderHelper.NotFoundException e) {
+ Log.d(Constants.TAG, "key not found, trying next signature...");
+ }
+ }
+ }
+
+ public void updateSignatureWithCleartext(byte[] clearText) throws IOException, SignatureException {
+
+ InputStream sigIn = new BufferedInputStream(new ByteArrayInputStream(clearText));
+
+ ByteArrayOutputStream outputBuffer = new ByteArrayOutputStream();
+
+ int lookAhead = readInputLine(outputBuffer, sigIn);
+
+ processLine(signature, outputBuffer.toByteArray());
+
+ while (lookAhead != -1) {
+ lookAhead = readInputLine(outputBuffer, lookAhead, sigIn);
+
+ signature.update((byte) '\r');
+ signature.update((byte) '\n');
+
+ processLine(signature, outputBuffer.toByteArray());
+ }
+
+ }
+
+ public void updateSignatureData(byte[] buf, int off, int len) {
+ if (signature != null) {
+ signature.update(buf, off, len);
+ } else if (onePassSignature != null) {
+ onePassSignature.update(buf, off, len);
+ }
+ }
+
+ void verifySignature(OperationLog log, int indent) throws PGPException {
+
+ log.add(LogType.MSG_DC_CLEAR_SIGNATURE_CHECK, indent);
+
+ // Verify signature
+ boolean validSignature = signature.verify();
+ if (validSignature) {
+ log.add(LogType.MSG_DC_CLEAR_SIGNATURE_OK, indent + 1);
+ } else {
+ log.add(LogType.MSG_DC_CLEAR_SIGNATURE_BAD, indent + 1);
+ }
+
+ // check for insecure hash algorithms
+ if (!PgpSecurityConstants.isSecureHashAlgorithm(signature.getHashAlgorithm())) {
+ log.add(LogType.MSG_DC_INSECURE_HASH_ALGO, indent + 1);
+ signatureResultBuilder.setInsecure(true);
+ }
+
+ signatureResultBuilder.setValidSignature(validSignature);
+
+ }
+
+ boolean verifySignatureOnePass(Object o, OperationLog log, int indent) throws PGPException {
+
+ if (!(o instanceof PGPSignatureList)) {
+ log.add(LogType.MSG_DC_ERROR_NO_SIGNATURE, indent);
+ return false;
+ }
+ PGPSignatureList signatureList = (PGPSignatureList) o;
+ if (signatureList.size() <= signatureIndex) {
+ log.add(LogType.MSG_DC_ERROR_NO_SIGNATURE, indent);
+ return false;
+ }
+
+ // PGPOnePassSignature and PGPSignature packets are "bracketed",
+ // so we need to take the last-minus-index'th element here
+ PGPSignature messageSignature = signatureList.get(signatureList.size() - 1 - signatureIndex);
+
+ // Verify signature
+ boolean validSignature = onePassSignature.verify(messageSignature);
+ if (validSignature) {
+ log.add(LogType.MSG_DC_CLEAR_SIGNATURE_OK, indent + 1);
+ } else {
+ log.add(LogType.MSG_DC_CLEAR_SIGNATURE_BAD, indent + 1);
+ }
+
+ // check for insecure hash algorithms
+ if (!PgpSecurityConstants.isSecureHashAlgorithm(onePassSignature.getHashAlgorithm())) {
+ log.add(LogType.MSG_DC_INSECURE_HASH_ALGO, indent + 1);
+ signatureResultBuilder.setInsecure(true);
+ }
+
+ signatureResultBuilder.setValidSignature(validSignature);
+
+ return true;
+
+ }
+
+ public byte[] getSigningFingerprint() {
+ return signingKey.getFingerprint();
+ }
+
+ public OpenPgpSignatureResult getSignatureResult() {
+ return signatureResultBuilder.build();
+ }
+
+ /**
+ * Mostly taken from ClearSignedFileProcessor in Bouncy Castle
+ */
+
+ private static void processLine(PGPSignature sig, byte[] line)
+ throws SignatureException {
+ int length = getLengthWithoutWhiteSpace(line);
+ if (length > 0) {
+ sig.update(line, 0, length);
+ }
+ }
+
+ private static int readInputLine(ByteArrayOutputStream bOut, InputStream fIn)
+ throws IOException {
+ bOut.reset();
+
+ int lookAhead = -1;
+ int ch;
+
+ while ((ch = fIn.read()) >= 0) {
+ bOut.write(ch);
+ if (ch == '\r' || ch == '\n') {
+ lookAhead = readPastEOL(bOut, ch, fIn);
+ break;
+ }
+ }
+
+ return lookAhead;
+ }
+
+ private static int readInputLine(ByteArrayOutputStream bOut, int lookAhead, InputStream fIn)
+ throws IOException {
+ bOut.reset();
+
+ int ch = lookAhead;
+
+ do {
+ bOut.write(ch);
+ if (ch == '\r' || ch == '\n') {
+ lookAhead = readPastEOL(bOut, ch, fIn);
+ break;
+ }
+ } while ((ch = fIn.read()) >= 0);
+
+ if (ch < 0) {
+ lookAhead = -1;
+ }
+
+ return lookAhead;
+ }
+
+ private static int readPastEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn)
+ throws IOException {
+ int lookAhead = fIn.read();
+
+ if (lastCh == '\r' && lookAhead == '\n') {
+ bOut.write(lookAhead);
+ lookAhead = fIn.read();
+ }
+
+ return lookAhead;
+ }
+
+ private static int getLengthWithoutWhiteSpace(byte[] line) {
+ int end = line.length - 1;
+
+ while (end >= 0 && isWhiteSpace(line[end])) {
+ end--;
+ }
+
+ return end + 1;
+ }
+
+ private static boolean isWhiteSpace(byte b) {
+ return b == '\r' || b == '\n' || b == '\t' || b == ' ';
+ }
+
+}
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 ca98882d8..c967a5abc 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java
@@ -18,6 +18,23 @@
package org.sufficientlysecure.keychain.pgp;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.TreeSet;
+
import org.spongycastle.bcpg.ArmoredOutputStream;
import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
import org.spongycastle.bcpg.SignatureSubpacketTags;
@@ -43,23 +60,6 @@ import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Utf8Util;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-import java.util.TimeZone;
-import java.util.TreeSet;
-
/** Wrapper around PGPKeyRing class, to be constructed from bytes.
*
* This class and its relatives UncachedPublicKey and UncachedSecretKey are
@@ -78,12 +78,13 @@ import java.util.TreeSet;
* @see org.sufficientlysecure.keychain.pgp.UncachedSecretKey
*
*/
-@SuppressWarnings("unchecked")
-public class UncachedKeyRing implements Serializable {
+public class UncachedKeyRing {
final PGPKeyRing mRing;
final boolean mIsSecret;
+ private static final int CANONICALIZE_MAX_USER_IDS = 100;
+
UncachedKeyRing(PGPKeyRing ring) {
mRing = ring;
mIsSecret = ring instanceof PGPSecretKeyRing;
@@ -457,11 +458,15 @@ public class UncachedKeyRing implements Serializable {
// check for duplicate user ids
if (processedUserIds.contains(userId)) {
- log.add(LogType.MSG_KC_UID_DUP,
- indent, userId);
+ log.add(LogType.MSG_KC_UID_DUP, indent, userId);
// strip out the first found user id with this name
modified = PGPPublicKey.removeCertification(modified, rawUserId);
}
+ if (processedUserIds.size() > CANONICALIZE_MAX_USER_IDS) {
+ log.add(LogType.MSG_KC_UID_TOO_MANY, indent, userId);
+ // strip out the user id
+ modified = PGPPublicKey.removeCertification(modified, rawUserId);
+ }
processedUserIds.add(userId);
PGPSignature selfCert = null;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java
index d7fb738fc..752c13007 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java
@@ -54,7 +54,7 @@ import java.io.IOException;
*/
public class KeychainDatabase extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "openkeychain.db";
- private static final int DATABASE_VERSION = 12;
+ private static final int DATABASE_VERSION = 14;
static Boolean apgHack = false;
private Context mContext;
@@ -79,7 +79,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
private static final String CREATE_KEYRINGS_SECRET =
"CREATE TABLE IF NOT EXISTS keyrings_secret ("
+ KeyRingsColumns.MASTER_KEY_ID + " INTEGER PRIMARY KEY,"
- + KeyRingsColumns.KEY_RING_DATA + " BLOB,"
+ + KeyRingsColumns.KEY_RING_DATA + " BLOB, "
+ "FOREIGN KEY(" + KeyRingsColumns.MASTER_KEY_ID + ") "
+ "REFERENCES keyrings_public(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE"
+ ")";
@@ -220,6 +220,13 @@ public class KeychainDatabase extends SQLiteOpenHelper {
db.execSQL(CREATE_API_APPS);
db.execSQL(CREATE_API_APPS_ACCOUNTS);
db.execSQL(CREATE_API_APPS_ALLOWED_KEYS);
+
+ db.execSQL("CREATE INDEX keys_by_rank ON keys (" + KeysColumns.RANK + ");");
+ db.execSQL("CREATE INDEX uids_by_rank ON user_packets (" + UserPacketsColumns.RANK + ", "
+ + UserPacketsColumns.USER_ID + ", " + UserPacketsColumns.MASTER_KEY_ID + ");");
+ db.execSQL("CREATE INDEX verified_certs ON certs ("
+ + CertsColumns.VERIFIED + ", " + CertsColumns.MASTER_KEY_ID + ");");
+
}
@Override
@@ -291,11 +298,14 @@ public class KeychainDatabase extends SQLiteOpenHelper {
db.execSQL("DELETE FROM api_accounts WHERE key_id BETWEEN 0 AND 3");
case 12:
db.execSQL(CREATE_UPDATE_KEYS);
- if (oldVersion == 10) {
- // no consolidate if we are updating from 10, we're just here for
- // the api_accounts fix and the new update keys table
- return;
- }
+ case 13:
+ // do nothing here, just consolidate
+ case 14:
+ db.execSQL("CREATE INDEX keys_by_rank ON keys (" + KeysColumns.RANK + ");");
+ db.execSQL("CREATE INDEX uids_by_rank ON user_packets (" + UserPacketsColumns.RANK + ", "
+ + UserPacketsColumns.USER_ID + ", " + UserPacketsColumns.MASTER_KEY_ID + ");");
+ db.execSQL("CREATE INDEX verified_certs ON certs ("
+ + CertsColumns.VERIFIED + ", " + CertsColumns.MASTER_KEY_ID + ");");
}
@@ -306,6 +316,17 @@ public class KeychainDatabase extends SQLiteOpenHelper {
mContext.getApplicationContext().startActivity(consolidateIntent);
}
+ @Override
+ public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ // Downgrade is ok for the debug version, makes it easier to work with branches
+ if (Constants.DEBUG) {
+ return;
+ }
+ // NOTE: downgrading the database is explicitly not allowed to prevent
+ // someone from exploiting old bugs to export the database
+ throw new RuntimeException("Downgrading the database is not allowed!");
+ }
+
/** This method tries to import data from a provided database.
*
* The sole assumptions made on this db are that there is a key_rings table
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java
index d722fa9e7..104343074 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java
@@ -303,14 +303,14 @@ public class KeychainProvider extends ContentProvider {
projectionMap.put(KeyRings.FINGERPRINT, Tables.KEYS + "." + Keys.FINGERPRINT);
projectionMap.put(KeyRings.USER_ID, Tables.USER_PACKETS + "." + UserPackets.USER_ID);
projectionMap.put(KeyRings.HAS_DUPLICATE_USER_ID,
- "(SELECT COUNT (*) FROM " + Tables.USER_PACKETS + " AS dups"
+ "(EXISTS (SELECT * FROM " + Tables.USER_PACKETS + " AS dups"
+ " WHERE dups." + UserPackets.MASTER_KEY_ID
+ " != " + Tables.KEYS + "." + Keys.MASTER_KEY_ID
+ " AND dups." + UserPackets.RANK + " = 0"
+ " AND dups." + UserPackets.USER_ID
+ " = "+ Tables.USER_PACKETS + "." + UserPackets.USER_ID
- + ") AS " + KeyRings.HAS_DUPLICATE_USER_ID);
- projectionMap.put(KeyRings.VERIFIED, KeyRings.VERIFIED);
+ + ")) AS " + KeyRings.HAS_DUPLICATE_USER_ID);
+ projectionMap.put(KeyRings.VERIFIED, Tables.CERTS + "." + Certs.VERIFIED);
projectionMap.put(KeyRings.PUBKEY_DATA,
Tables.KEY_RINGS_PUBLIC + "." + KeyRingData.KEY_RING_DATA
+ " AS " + KeyRings.PUBKEY_DATA);
@@ -319,10 +319,8 @@ public class KeychainProvider extends ContentProvider {
+ " AS " + KeyRings.PRIVKEY_DATA);
projectionMap.put(KeyRings.HAS_SECRET, Tables.KEYS + "." + KeyRings.HAS_SECRET);
projectionMap.put(KeyRings.HAS_ANY_SECRET,
- "(EXISTS (SELECT * FROM " + Tables.KEY_RINGS_SECRET
- + " WHERE " + Tables.KEY_RINGS_SECRET + "." + KeyRingData.MASTER_KEY_ID
- + " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID
- + ")) AS " + KeyRings.HAS_ANY_SECRET);
+ "(" + Tables.KEY_RINGS_SECRET + "." + KeyRings.MASTER_KEY_ID + " IS NOT NULL)" +
+ " AS " + KeyRings.HAS_ANY_SECRET);
projectionMap.put(KeyRings.HAS_ENCRYPT,
"kE." + Keys.KEY_ID + " AS " + KeyRings.HAS_ENCRYPT);
projectionMap.put(KeyRings.HAS_SIGN,
@@ -363,7 +361,7 @@ public class KeychainProvider extends ContentProvider {
+ " = "
+ Tables.KEY_RINGS_PUBLIC + "." + KeyRingData.MASTER_KEY_ID
+ ")" : "")
- + (plist.contains(KeyRings.PRIVKEY_DATA) ?
+ + (plist.contains(KeyRings.PRIVKEY_DATA) || plist.contains(KeyRings.HAS_ANY_SECRET) ?
" LEFT JOIN " + Tables.KEY_RINGS_SECRET + " ON ("
+ Tables.KEYS + "." + Keys.MASTER_KEY_ID
+ " = "
@@ -712,20 +710,45 @@ public class KeychainProvider extends ContentProvider {
}
SQLiteDatabase db = getDb().getReadableDatabase();
+
Cursor cursor = qb.query(db, projection, selection, selectionArgs, groupBy, having, orderBy);
if (cursor != null) {
// Tell the cursor what uri to watch, so it knows when its source data changes
cursor.setNotificationUri(getContext().getContentResolver(), uri);
}
+ Log.d(Constants.TAG,
+ "Query: " + qb.buildQuery(projection, selection, null, null, orderBy, null));
+
if (Constants.DEBUG && Constants.DEBUG_LOG_DB_QUERIES) {
- Log.d(Constants.TAG,
- "Query: "
- + qb.buildQuery(projection, selection, selectionArgs, null, null,
- orderBy, null));
Log.d(Constants.TAG, "Cursor: " + DatabaseUtils.dumpCursorToString(cursor));
}
+ if (Constants.DEBUG && Constants.DEBUG_EXPLAIN_QUERIES) {
+ String rawQuery = qb.buildQuery(projection, selection, groupBy, having, orderBy, null);
+ Cursor explainCursor = db.rawQuery("EXPLAIN QUERY PLAN " + rawQuery, selectionArgs);
+
+ // this is a debugging feature, we can be a little careless
+ explainCursor.moveToFirst();
+
+ StringBuilder line = new StringBuilder();
+ for (int i = 0; i < explainCursor.getColumnCount(); i++) {
+ line.append(explainCursor.getColumnName(i)).append(", ");
+ }
+ Log.d(Constants.TAG, line.toString());
+
+ while (!explainCursor.isAfterLast()) {
+ line = new StringBuilder();
+ for (int i = 0; i < explainCursor.getColumnCount(); i++) {
+ line.append(explainCursor.getString(i)).append(", ");
+ }
+ Log.d(Constants.TAG, line.toString());
+ explainCursor.moveToNext();
+ }
+
+ explainCursor.close();
+ }
+
return cursor;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java
index a6823d3ac..375775ff1 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java
@@ -62,7 +62,6 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
-import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
import org.sufficientlysecure.keychain.provider.KeychainContract.UpdatedKeys;
import org.sufficientlysecure.keychain.remote.AccountSettings;
import org.sufficientlysecure.keychain.remote.AppSettings;
@@ -968,7 +967,7 @@ public class ProviderHelper {
// If we have an expected fingerprint, make sure it matches
if (expectedFingerprint != null) {
- if (!canPublicRing.containsSubkey(expectedFingerprint)) {
+ if (!canPublicRing.containsBoundSubkey(expectedFingerprint)) {
log(LogType.MSG_IP_FINGERPRINT_ERROR);
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
} else {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryFileProvider.java
index 67f2c36bc..68963d595 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryFileProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2014-2015 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
*
* This program is free software: you can redistribute it and/or modify
@@ -29,7 +29,7 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
-import android.provider.OpenableColumns;
+import android.provider.MediaStore;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.util.DatabaseUtil;
@@ -39,18 +39,20 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.UUID;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* TemporaryStorageProvider stores decrypted files inside the app's cache directory previously to
* sharing them with other applications.
- *
+ * <p/>
* Security:
* - It is writable by OpenKeychain only (see Manifest), but exported for reading files
* - It uses UUIDs as identifiers which makes predicting files from outside impossible
* - Querying a number of files is not allowed, only querying single files
* -> You can only open a file if you know the Uri containing the precise UUID, this Uri is only
* revealed when the user shares a decrypted file with another app.
- *
+ * <p/>
* Why is support lib's FileProvider not used?
* Because granting Uri permissions temporarily does not work correctly. See
* - https://code.google.com/p/android/issues/detail?id=76683
@@ -59,30 +61,36 @@ import java.util.UUID;
* - http://stackoverflow.com/q/18249007
* - Comments at http://www.blogc.at/2014/03/23/share-private-files-with-other-apps-fileprovider/
*/
-public class TemporaryStorageProvider extends ContentProvider {
+public class TemporaryFileProvider extends ContentProvider {
private static final String DB_NAME = "tempstorage.db";
private static final String TABLE_FILES = "files";
- private static final String COLUMN_ID = "id";
- private static final String COLUMN_NAME = "name";
- private static final String COLUMN_TIME = "time";
- private static final String COLUMN_TYPE = "mimetype";
- public static final String AUTHORITY = Constants.TEMPSTORAGE_AUTHORITY;
+ public static final String AUTHORITY = Constants.TEMP_FILE_PROVIDER_AUTHORITY;
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);
private static final int DB_VERSION = 3;
- private static File cacheDir;
+ interface TemporaryFileColumns {
+ String COLUMN_UUID = "id";
+ String COLUMN_NAME = "name";
+ String COLUMN_TIME = "time";
+ String COLUMN_TYPE = "mimetype";
+ }
+
+ private static final String TEMP_FILES_DIR = "temp";
+ private static File tempFilesDir;
+
+ private static Pattern UUID_PATTERN = Pattern.compile("[a-fA-F0-9-]+");
public static Uri createFile(Context context, String targetName, String mimeType) {
ContentValues contentValues = new ContentValues();
- contentValues.put(COLUMN_NAME, targetName);
- contentValues.put(COLUMN_TYPE, mimeType);
+ contentValues.put(TemporaryFileColumns.COLUMN_NAME, targetName);
+ contentValues.put(TemporaryFileColumns.COLUMN_TYPE, mimeType);
return context.getContentResolver().insert(CONTENT_URI, contentValues);
}
public static Uri createFile(Context context, String targetName) {
ContentValues contentValues = new ContentValues();
- contentValues.put(COLUMN_NAME, targetName);
+ contentValues.put(TemporaryFileColumns.COLUMN_NAME, targetName);
return context.getContentResolver().insert(CONTENT_URI, contentValues);
}
@@ -93,13 +101,16 @@ public class TemporaryStorageProvider extends ContentProvider {
public static int setMimeType(Context context, Uri uri, String mimetype) {
ContentValues values = new ContentValues();
- values.put(COLUMN_TYPE, mimetype);
+ values.put(TemporaryFileColumns.COLUMN_TYPE, mimetype);
return context.getContentResolver().update(uri, values, null, null);
}
public static int cleanUp(Context context) {
- return context.getContentResolver().delete(CONTENT_URI, COLUMN_TIME + "< ?",
- new String[]{Long.toString(System.currentTimeMillis() - Constants.TEMPFILE_TTL)});
+ return context.getContentResolver().delete(
+ CONTENT_URI,
+ TemporaryFileColumns.COLUMN_TIME + "< ?",
+ new String[]{Long.toString(System.currentTimeMillis() - Constants.TEMPFILE_TTL)}
+ );
}
private class TemporaryStorageDatabase extends SQLiteOpenHelper {
@@ -111,10 +122,10 @@ public class TemporaryStorageProvider extends ContentProvider {
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_FILES + " (" +
- COLUMN_ID + " TEXT PRIMARY KEY, " +
- COLUMN_NAME + " TEXT, " +
- COLUMN_TYPE + " TEXT, " +
- COLUMN_TIME + " INTEGER" +
+ TemporaryFileColumns.COLUMN_UUID + " TEXT PRIMARY KEY, " +
+ TemporaryFileColumns.COLUMN_NAME + " TEXT, " +
+ TemporaryFileColumns.COLUMN_TYPE + " TEXT, " +
+ TemporaryFileColumns.COLUMN_TIME + " INTEGER" +
");");
}
@@ -126,12 +137,12 @@ public class TemporaryStorageProvider extends ContentProvider {
case 1:
db.execSQL("DROP TABLE IF EXISTS files");
db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_FILES + " (" +
- COLUMN_ID + " TEXT PRIMARY KEY, " +
- COLUMN_NAME + " TEXT, " +
- COLUMN_TIME + " INTEGER" +
+ TemporaryFileColumns.COLUMN_UUID + " TEXT PRIMARY KEY, " +
+ TemporaryFileColumns.COLUMN_NAME + " TEXT, " +
+ TemporaryFileColumns.COLUMN_TIME + " INTEGER" +
");");
case 2:
- db.execSQL("ALTER TABLE files ADD COLUMN " + COLUMN_TYPE + " TEXT");
+ db.execSQL("ALTER TABLE files ADD COLUMN " + TemporaryFileColumns.COLUMN_TYPE + " TEXT");
}
}
}
@@ -147,14 +158,19 @@ public class TemporaryStorageProvider extends ContentProvider {
}
private File getFile(String id) {
- return new File(cacheDir, "temp/" + id);
+ Matcher m = UUID_PATTERN.matcher(id);
+ if (!m.matches()) {
+ throw new SecurityException("Can only open temporary files with UUIDs!");
+ }
+
+ return new File(tempFilesDir, id);
}
@Override
public boolean onCreate() {
db = new TemporaryStorageDatabase(getContext());
- cacheDir = getContext().getCacheDir();
- return new File(cacheDir, "temp").mkdirs();
+ tempFilesDir = new File(getContext().getCacheDir(), TEMP_FILES_DIR);
+ return tempFilesDir.mkdirs();
}
@Override
@@ -163,27 +179,24 @@ public class TemporaryStorageProvider extends ContentProvider {
throw new SecurityException("Listing temporary files is not allowed, only querying single files.");
}
- Log.d(Constants.TAG, "being asked for file " + uri);
-
File file;
try {
file = getFile(uri);
- if (file.exists()) {
- Log.e(Constants.TAG, "already exists");
- }
} catch (FileNotFoundException e) {
Log.e(Constants.TAG, "file not found!");
return null;
}
- Cursor fileName = db.getReadableDatabase().query(TABLE_FILES, new String[]{COLUMN_NAME}, COLUMN_ID + "=?",
+ Cursor fileName = db.getReadableDatabase().query(TABLE_FILES,
+ new String[]{TemporaryFileColumns.COLUMN_NAME},
+ TemporaryFileColumns.COLUMN_UUID + "=?",
new String[]{uri.getLastPathSegment()}, null, null, null);
if (fileName != null) {
if (fileName.moveToNext()) {
MatrixCursor cursor = new MatrixCursor(new String[]{
- OpenableColumns.DISPLAY_NAME,
- OpenableColumns.SIZE,
- "_data"
+ MediaStore.MediaColumns.DISPLAY_NAME,
+ MediaStore.MediaColumns.SIZE,
+ MediaStore.MediaColumns.DATA,
});
cursor.newRow()
.add(fileName.getString(0))
@@ -200,7 +213,8 @@ public class TemporaryStorageProvider extends ContentProvider {
@Override
public String getType(Uri uri) {
Cursor cursor = db.getReadableDatabase().query(TABLE_FILES,
- new String[]{COLUMN_TYPE}, COLUMN_ID + "=?",
+ new String[]{TemporaryFileColumns.COLUMN_TYPE},
+ TemporaryFileColumns.COLUMN_UUID + "=?",
new String[]{uri.getLastPathSegment()}, null, null, null);
if (cursor != null) {
try {
@@ -227,11 +241,11 @@ public class TemporaryStorageProvider extends ContentProvider {
@Override
public Uri insert(Uri uri, ContentValues values) {
- if (!values.containsKey(COLUMN_TIME)) {
- values.put(COLUMN_TIME, System.currentTimeMillis());
+ if (!values.containsKey(TemporaryFileColumns.COLUMN_TIME)) {
+ values.put(TemporaryFileColumns.COLUMN_TIME, System.currentTimeMillis());
}
String uuid = UUID.randomUUID().toString();
- values.put(COLUMN_ID, uuid);
+ values.put(TemporaryFileColumns.COLUMN_UUID, uuid);
int insert = (int) db.getWritableDatabase().insert(TABLE_FILES, null, values);
if (insert == -1) {
Log.e(Constants.TAG, "Insert failed!");
@@ -252,10 +266,10 @@ public class TemporaryStorageProvider extends ContentProvider {
return 0;
}
- selection = DatabaseUtil.concatenateWhere(selection, COLUMN_ID + "=?");
+ selection = DatabaseUtil.concatenateWhere(selection, TemporaryFileColumns.COLUMN_UUID + "=?");
selectionArgs = DatabaseUtil.appendSelectionArgs(selectionArgs, new String[]{uri.getLastPathSegment()});
- Cursor files = db.getReadableDatabase().query(TABLE_FILES, new String[]{COLUMN_ID}, selection,
+ Cursor files = db.getReadableDatabase().query(TABLE_FILES, new String[]{TemporaryFileColumns.COLUMN_UUID}, selection,
selectionArgs, null, null, null);
if (files != null) {
while (files.moveToNext()) {
@@ -269,19 +283,18 @@ public class TemporaryStorageProvider extends ContentProvider {
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- if (values.size() != 1 || !values.containsKey(COLUMN_TYPE)) {
+ if (values.size() != 1 || !values.containsKey(TemporaryFileColumns.COLUMN_TYPE)) {
throw new UnsupportedOperationException("Update supported only for type field!");
}
if (selection != null || selectionArgs != null) {
throw new UnsupportedOperationException("Update supported only for plain uri!");
}
return db.getWritableDatabase().update(TABLE_FILES, values,
- COLUMN_ID + " = ?", new String[]{uri.getLastPathSegment()});
+ TemporaryFileColumns.COLUMN_UUID + " = ?", new String[]{uri.getLastPathSegment()});
}
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
- Log.d(Constants.TAG, "openFile");
return openFileHelper(uri, mode);
}
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 e7709e58e..b810f5a6a 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java
@@ -25,6 +25,7 @@ import android.net.Uri;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
@@ -34,13 +35,15 @@ import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.OpenPgpMetadata;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.openintents.openpgp.util.OpenPgpApi;
+import org.spongycastle.bcpg.ArmoredOutputStream;
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.PgpSecurityConstants;
-import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation;
+import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
+import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation;
+import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants;
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptInputParcel;
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
@@ -238,10 +241,8 @@ public class OpenPgpService extends RemoteService {
PendingIntent.FLAG_CANCEL_CURRENT);
}
- private Intent signImpl(Intent data, ParcelFileDescriptor input,
- ParcelFileDescriptor output, boolean cleartextSign) {
- InputStream is = null;
- OutputStream os = null;
+ private Intent signImpl(Intent data, InputStream inputStream,
+ OutputStream outputStream, boolean cleartextSign) {
try {
boolean asciiArmor = cleartextSign || data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
@@ -277,14 +278,13 @@ public class OpenPgpService extends RemoteService {
}
// Get Input- and OutputStream from ParcelFileDescriptor
- is = new ParcelFileDescriptor.AutoCloseInputStream(input);
- if (cleartextSign) {
+ if (!cleartextSign) {
// output stream only needed for cleartext signatures,
// detached signatures are returned as extra
- os = new ParcelFileDescriptor.AutoCloseOutputStream(output);
+ outputStream = null;
}
- long inputLength = is.available();
- InputData inputData = new InputData(is, inputLength);
+ long inputLength = inputStream.available();
+ InputData inputData = new InputData(inputStream, inputLength);
CryptoInputParcel inputParcel = CryptoInputParcelCacheService.getCryptoInputParcel(this, data);
if (inputParcel == null) {
@@ -298,7 +298,7 @@ public class OpenPgpService extends RemoteService {
// execute PGP operation!
PgpSignEncryptOperation pse = new PgpSignEncryptOperation(this, new ProviderHelper(getContext()), null);
- PgpSignEncryptResult pgpResult = pse.execute(pseInput, inputParcel, inputData, os);
+ PgpSignEncryptResult pgpResult = pse.execute(pseInput, inputParcel, inputData, outputStream);
if (pgpResult.isPending()) {
@@ -330,28 +330,11 @@ public class OpenPgpService extends RemoteService {
new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
return result;
- } finally {
- if (is != null) {
- try {
- is.close();
- } catch (IOException e) {
- Log.e(Constants.TAG, "IOException when closing InputStream", e);
- }
- }
- if (os != null) {
- try {
- os.close();
- } catch (IOException e) {
- Log.e(Constants.TAG, "IOException when closing OutputStream", e);
- }
- }
}
}
- private Intent encryptAndSignImpl(Intent data, ParcelFileDescriptor input,
- ParcelFileDescriptor output, boolean sign) {
- InputStream is = null;
- OutputStream os = null;
+ private Intent encryptAndSignImpl(Intent data, InputStream inputStream,
+ OutputStream outputStream, boolean sign) {
try {
boolean asciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
String originalFilename = data.getStringExtra(OpenPgpApi.EXTRA_ORIGINAL_FILENAME);
@@ -383,13 +366,9 @@ public class OpenPgpService extends RemoteService {
}
}
- // build InputData and write into OutputStream
- // Get Input- and OutputStream from ParcelFileDescriptor
- is = new ParcelFileDescriptor.AutoCloseInputStream(input);
- os = new ParcelFileDescriptor.AutoCloseOutputStream(output);
-
- long inputLength = is.available();
- InputData inputData = new InputData(is, inputLength, originalFilename);
+ // TODO this is not correct!
+ long inputLength = inputStream.available();
+ InputData inputData = new InputData(inputStream, inputLength, originalFilename);
PgpSignEncryptInputParcel pseInput = new PgpSignEncryptInputParcel();
pseInput.setEnableAsciiArmorOutput(asciiArmor)
@@ -455,7 +434,7 @@ public class OpenPgpService extends RemoteService {
PgpSignEncryptOperation op = new PgpSignEncryptOperation(this, new ProviderHelper(getContext()), null);
// execute PGP operation!
- PgpSignEncryptResult pgpResult = op.execute(pseInput, inputParcel, inputData, os);
+ PgpSignEncryptResult pgpResult = op.execute(pseInput, inputParcel, inputData, outputStream);
if (pgpResult.isPending()) {
RequiredInputParcel requiredInput = pgpResult.getRequiredInputParcel();
@@ -482,37 +461,15 @@ public class OpenPgpService extends RemoteService {
new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
return result;
- } finally {
- if (is != null) {
- try {
- is.close();
- } catch (IOException e) {
- Log.e(Constants.TAG, "IOException when closing InputStream", e);
- }
- }
- if (os != null) {
- try {
- os.close();
- } catch (IOException e) {
- Log.e(Constants.TAG, "IOException when closing OutputStream", e);
- }
- }
}
}
- private Intent decryptAndVerifyImpl(Intent data, ParcelFileDescriptor inputDescriptor,
- ParcelFileDescriptor output, boolean decryptMetadataOnly) {
- InputStream inputStream = null;
- OutputStream outputStream = null;
+ private Intent decryptAndVerifyImpl(Intent data, InputStream inputStream,
+ OutputStream outputStream, boolean decryptMetadataOnly) {
try {
- // Get Input- and OutputStream from ParcelFileDescriptor
- inputStream = new ParcelFileDescriptor.AutoCloseInputStream(inputDescriptor);
-
// output is optional, e.g., for verifying detached signatures
- if (decryptMetadataOnly || output == null) {
+ if (decryptMetadataOnly) {
outputStream = null;
- } else {
- outputStream = new ParcelFileDescriptor.AutoCloseOutputStream(output);
}
String currentPkg = getCurrentCallingPackage();
@@ -538,6 +495,7 @@ public class OpenPgpService extends RemoteService {
PgpDecryptVerifyOperation op = new PgpDecryptVerifyOperation(this, mProviderHelper, null);
+ // TODO this is not correct!
long inputLength = inputStream.available();
InputData inputData = new InputData(inputStream, inputLength);
@@ -604,6 +562,7 @@ public class OpenPgpService extends RemoteService {
// case RESULT_NOT_ENCRYPTED, but a signature, fallback to deprecated signatureOnly variable
if (decryptionResult.getResult() == OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED
&& signatureResult.getResult() != OpenPgpSignatureResult.RESULT_NO_SIGNATURE) {
+ // noinspection deprecation, TODO
signatureResult.setSignatureOnly(true);
}
@@ -665,35 +624,40 @@ public class OpenPgpService extends RemoteService {
result.putExtra(OpenPgpApi.RESULT_ERROR, new OpenPgpError(OpenPgpError.GENERIC_ERROR, e.getMessage()));
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
return result;
- } finally {
- if (inputStream != null) {
- try {
- inputStream.close();
- } catch (IOException e) {
- Log.e(Constants.TAG, "IOException when closing InputStream", e);
- }
- }
- if (outputStream != null) {
- try {
- outputStream.close();
- } catch (IOException e) {
- Log.e(Constants.TAG, "IOException when closing OutputStream", e);
- }
- }
}
}
- private Intent getKeyImpl(Intent data) {
+ private Intent getKeyImpl(Intent data, OutputStream outputStream) {
try {
long masterKeyId = data.getLongExtra(OpenPgpApi.EXTRA_KEY_ID, 0);
try {
// try to find key, throws NotFoundException if not in db!
- mProviderHelper.getCanonicalizedPublicKeyRing(masterKeyId);
+ CanonicalizedPublicKeyRing keyRing =
+ mProviderHelper.getCanonicalizedPublicKeyRing(masterKeyId);
Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
+
+ boolean requestedKeyData = outputStream != null;
+ if (requestedKeyData) {
+ boolean requestAsciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, false);
+
+ try {
+ if (requestAsciiArmor) {
+ outputStream = new ArmoredOutputStream(outputStream);
+ }
+ keyRing.encode(outputStream);
+ } finally {
+ try {
+ outputStream.close();
+ } catch (IOException e) {
+ Log.e(Constants.TAG, "IOException when closing OutputStream", e);
+ }
+ }
+ }
+
// also return PendingIntent that opens the key view activity
result.putExtra(OpenPgpApi.RESULT_INTENT, getShowKeyPendingIntent(masterKeyId));
@@ -821,7 +785,7 @@ public class OpenPgpService extends RemoteService {
OpenPgpError error = new OpenPgpError
(OpenPgpError.INCOMPATIBLE_API_VERSIONS, "Incompatible API versions!\n"
+ "used API version: " + data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) + "\n"
- + "supported API versions: " + supportedVersions.toString());
+ + "supported API versions: " + supportedVersions);
result.putExtra(OpenPgpApi.RESULT_ERROR, error);
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
return result;
@@ -850,68 +814,88 @@ public class OpenPgpService extends RemoteService {
return mBinder;
}
+ @Nullable
+ protected Intent executeInternal(
+ @NonNull Intent data,
+ @Nullable ParcelFileDescriptor input,
+ @Nullable ParcelFileDescriptor output) {
- protected Intent executeInternal(Intent data, ParcelFileDescriptor input, ParcelFileDescriptor output) {
- try {
- Intent errorResult = checkRequirements(data);
- if (errorResult != null) {
- return errorResult;
- }
+ OutputStream outputStream =
+ (output != null) ? new ParcelFileDescriptor.AutoCloseOutputStream(output) : null;
+ InputStream inputStream =
+ (input != null) ? new ParcelFileDescriptor.AutoCloseInputStream(input) : null;
- String action = data.getAction();
- 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;
- }
- }
+ try {
+ return executeInternalWithStreams(data, inputStream, outputStream);
} finally {
// always close input and output file descriptors even in error cases
- if (input != null) {
+ if (inputStream != null) {
try {
- input.close();
+ inputStream.close();
} catch (IOException e) {
Log.e(Constants.TAG, "IOException when closing input ParcelFileDescriptor", e);
}
}
- if (output != null) {
+ if (outputStream != null) {
try {
- output.close();
+ outputStream.close();
} catch (IOException e) {
Log.e(Constants.TAG, "IOException when closing output ParcelFileDescriptor", e);
}
}
}
}
+
+ @Nullable
+ protected Intent executeInternalWithStreams(
+ @NonNull Intent data,
+ @Nullable InputStream inputStream,
+ @Nullable OutputStream outputStream) {
+
+ Intent errorResult = checkRequirements(data);
+ if (errorResult != null) {
+ return errorResult;
+ }
+
+ String action = data.getAction();
+ switch (action) {
+ case OpenPgpApi.ACTION_CLEARTEXT_SIGN: {
+ return signImpl(data, inputStream, outputStream, 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, inputStream, outputStream, true);
+ }
+ case OpenPgpApi.ACTION_DETACHED_SIGN: {
+ return signImpl(data, inputStream, outputStream, false);
+ }
+ case OpenPgpApi.ACTION_ENCRYPT: {
+ return encryptAndSignImpl(data, inputStream, outputStream, false);
+ }
+ case OpenPgpApi.ACTION_SIGN_AND_ENCRYPT: {
+ return encryptAndSignImpl(data, inputStream, outputStream, true);
+ }
+ case OpenPgpApi.ACTION_DECRYPT_VERIFY: {
+ return decryptAndVerifyImpl(data, inputStream, outputStream, false);
+ }
+ case OpenPgpApi.ACTION_DECRYPT_METADATA: {
+ return decryptAndVerifyImpl(data, inputStream, outputStream, 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, outputStream);
+ }
+ default: {
+ return null;
+ }
+ }
+
+ }
+
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/BackupKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/BackupKeyringParcel.java
new file mode 100644
index 000000000..3d9626934
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/BackupKeyringParcel.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
+ * Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.service;
+
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import org.sufficientlysecure.keychain.util.Passphrase;
+
+
+public class BackupKeyringParcel implements Parcelable {
+ public Uri mCanonicalizedPublicKeyringUri;
+ public Passphrase mSymmetricPassphrase;
+
+ public boolean mExportSecret;
+ public long mMasterKeyIds[];
+ public Uri mOutputUri;
+
+ public BackupKeyringParcel(Passphrase symmetricPassphrase,
+ long[] masterKeyIds, boolean exportSecret, Uri outputUri) {
+ mSymmetricPassphrase = symmetricPassphrase;
+ mMasterKeyIds = masterKeyIds;
+ mExportSecret = exportSecret;
+ mOutputUri = outputUri;
+ }
+
+ protected BackupKeyringParcel(Parcel in) {
+ mCanonicalizedPublicKeyringUri = (Uri) in.readValue(Uri.class.getClassLoader());
+ mExportSecret = in.readByte() != 0x00;
+ mOutputUri = (Uri) in.readValue(Uri.class.getClassLoader());
+ mMasterKeyIds = in.createLongArray();
+ mSymmetricPassphrase = in.readParcelable(getClass().getClassLoader());
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeValue(mCanonicalizedPublicKeyringUri);
+ dest.writeByte((byte) (mExportSecret ? 0x01 : 0x00));
+ dest.writeValue(mOutputUri);
+ dest.writeLongArray(mMasterKeyIds);
+ dest.writeParcelable(mSymmetricPassphrase, 0);
+ }
+
+ public static final Parcelable.Creator<BackupKeyringParcel> CREATOR = new Parcelable.Creator<BackupKeyringParcel>() {
+ @Override
+ public BackupKeyringParcel createFromParcel(Parcel in) {
+ return new BackupKeyringParcel(in);
+ }
+
+ @Override
+ public BackupKeyringParcel[] newArray(int size) {
+ return new BackupKeyringParcel[size];
+ }
+ };
+} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/BenchmarkInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/BenchmarkInputParcel.java
new file mode 100644
index 000000000..cfbdfefff
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/BenchmarkInputParcel.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
+ * Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.service;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+
+public class BenchmarkInputParcel implements Parcelable {
+
+ public BenchmarkInputParcel() {
+ }
+
+ protected BenchmarkInputParcel(Parcel in) {
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ }
+
+ public static final Creator<BenchmarkInputParcel> CREATOR = new Creator<BenchmarkInputParcel>() {
+ @Override
+ public BenchmarkInputParcel createFromParcel(Parcel in) {
+ return new BenchmarkInputParcel(in);
+ }
+
+ @Override
+ public BenchmarkInputParcel[] newArray(int size) {
+ return new BenchmarkInputParcel[size];
+ }
+ };
+} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ExportKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ExportKeyringParcel.java
deleted file mode 100644
index 24c002bbd..000000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ExportKeyringParcel.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
- * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
- * Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.com>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package org.sufficientlysecure.keychain.service;
-
-import android.net.Uri;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
-
-public class ExportKeyringParcel implements Parcelable {
- public String mKeyserver;
- public Uri mCanonicalizedPublicKeyringUri;
- public UncachedKeyRing mUncachedKeyRing;
-
- public boolean mExportSecret;
- public long mMasterKeyIds[];
- public String mOutputFile;
- public Uri mOutputUri;
- public ExportType mExportType;
-
- public enum ExportType {
- UPLOAD_KEYSERVER,
- EXPORT_FILE,
- EXPORT_URI
- }
-
- public ExportKeyringParcel(String keyserver, Uri keyringUri) {
- mExportType = ExportType.UPLOAD_KEYSERVER;
- mKeyserver = keyserver;
- mCanonicalizedPublicKeyringUri = keyringUri;
- }
-
- public ExportKeyringParcel(String keyserver, UncachedKeyRing uncachedKeyRing) {
- mExportType = ExportType.UPLOAD_KEYSERVER;
- mKeyserver = keyserver;
- mUncachedKeyRing = uncachedKeyRing;
- }
-
- public ExportKeyringParcel(long[] masterKeyIds, boolean exportSecret, String outputFile) {
- mExportType = ExportType.EXPORT_FILE;
- mMasterKeyIds = masterKeyIds;
- mExportSecret = exportSecret;
- mOutputFile = outputFile;
- }
-
- @SuppressWarnings("unused") // TODO: is it used?
- public ExportKeyringParcel(long[] masterKeyIds, boolean exportSecret, Uri outputUri) {
- mExportType = ExportType.EXPORT_URI;
- mMasterKeyIds = masterKeyIds;
- mExportSecret = exportSecret;
- mOutputUri = outputUri;
- }
-
- protected ExportKeyringParcel(Parcel in) {
- mKeyserver = in.readString();
- mCanonicalizedPublicKeyringUri = (Uri) in.readValue(Uri.class.getClassLoader());
- mUncachedKeyRing = (UncachedKeyRing) in.readValue(UncachedKeyRing.class.getClassLoader());
- mExportSecret = in.readByte() != 0x00;
- mOutputFile = in.readString();
- mOutputUri = (Uri) in.readValue(Uri.class.getClassLoader());
- mExportType = (ExportType) in.readValue(ExportType.class.getClassLoader());
- mMasterKeyIds = in.createLongArray();
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(mKeyserver);
- dest.writeValue(mCanonicalizedPublicKeyringUri);
- dest.writeValue(mUncachedKeyRing);
- dest.writeByte((byte) (mExportSecret ? 0x01 : 0x00));
- dest.writeString(mOutputFile);
- dest.writeValue(mOutputUri);
- dest.writeValue(mExportType);
- dest.writeLongArray(mMasterKeyIds);
- }
-
- public static final Parcelable.Creator<ExportKeyringParcel> CREATOR = new Parcelable.Creator<ExportKeyringParcel>() {
- @Override
- public ExportKeyringParcel createFromParcel(Parcel in) {
- return new ExportKeyringParcel(in);
- }
-
- @Override
- public ExportKeyringParcel[] newArray(int size) {
- return new ExportKeyringParcel[size];
- }
- };
-} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java
index c7ac92eef..cf51e3b55 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java
@@ -29,17 +29,19 @@ import android.os.RemoteException;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.operations.BaseOperation;
+import org.sufficientlysecure.keychain.operations.BenchmarkOperation;
import org.sufficientlysecure.keychain.operations.CertifyOperation;
import org.sufficientlysecure.keychain.operations.ConsolidateOperation;
import org.sufficientlysecure.keychain.operations.DeleteOperation;
import org.sufficientlysecure.keychain.operations.EditKeyOperation;
-import org.sufficientlysecure.keychain.operations.ExportOperation;
+import org.sufficientlysecure.keychain.operations.BackupOperation;
import org.sufficientlysecure.keychain.operations.ImportOperation;
import org.sufficientlysecure.keychain.operations.KeybaseVerificationOperation;
import org.sufficientlysecure.keychain.operations.InputDataOperation;
import org.sufficientlysecure.keychain.operations.PromoteKeyOperation;
import org.sufficientlysecure.keychain.operations.RevokeOperation;
import org.sufficientlysecure.keychain.operations.SignEncryptOperation;
+import org.sufficientlysecure.keychain.operations.UploadOperation;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
@@ -124,14 +126,18 @@ public class KeychainService extends Service implements Progressable {
op = new PromoteKeyOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled);
} else if (inputParcel instanceof ImportKeyringParcel) {
op = new ImportOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled);
- } else if (inputParcel instanceof ExportKeyringParcel) {
- op = new ExportOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled);
+ } else if (inputParcel instanceof BackupKeyringParcel) {
+ op = new BackupOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled);
+ } else if (inputParcel instanceof UploadKeyringParcel) {
+ op = new UploadOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled);
} else if (inputParcel instanceof ConsolidateInputParcel) {
op = new ConsolidateOperation(outerThis, new ProviderHelper(outerThis), outerThis);
} else if (inputParcel instanceof KeybaseVerificationParcel) {
op = new KeybaseVerificationOperation(outerThis, new ProviderHelper(outerThis), outerThis);
} else if (inputParcel instanceof InputDataParcel) {
op = new InputDataOperation(outerThis, new ProviderHelper(outerThis), outerThis);
+ } else if (inputParcel instanceof BenchmarkInputParcel) {
+ op = new BenchmarkOperation(outerThis, new ProviderHelper(outerThis), outerThis);
} else {
throw new AssertionError("Unrecognized input parcel in KeychainService!");
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java
index 8aebae7aa..122eb6cf4 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeyserverSyncAdapterService.java
@@ -59,11 +59,12 @@ public class KeyserverSyncAdapterService extends Service {
// time since last update after which a key should be updated again, in s
public static final long KEY_UPDATE_LIMIT =
Constants.DEBUG_KEYSERVER_SYNC ? 1 : TimeUnit.DAYS.toSeconds(7);
- // time by which a sync is postponed in case of a
+ // time by which a sync is postponed in case screen is on
public static final long SYNC_POSTPONE_TIME =
Constants.DEBUG_KEYSERVER_SYNC ? 30 * 1000 : TimeUnit.MINUTES.toMillis(5);
// Time taken by Orbot before a new circuit is created
- public static final int ORBOT_CIRCUIT_TIMEOUT = (int) TimeUnit.MINUTES.toMillis(10);
+ public static final int ORBOT_CIRCUIT_TIMEOUT_SECONDS =
+ Constants.DEBUG_KEYSERVER_SYNC ? 2 : (int) TimeUnit.MINUTES.toSeconds(10);
private static final String ACTION_IGNORE_TOR = "ignore_tor";
@@ -77,10 +78,14 @@ public class KeyserverSyncAdapterService extends Service {
@Override
public int onStartCommand(final Intent intent, int flags, final int startId) {
+ if (intent == null || intent.getAction() == null) {
+ // introduced due to https://github.com/open-keychain/open-keychain/issues/1573
+ return START_NOT_STICKY; // we can't act on this Intent and don't want it redelivered
+ }
switch (intent.getAction()) {
case ACTION_CANCEL: {
mCancelled.set(true);
- break;
+ return START_NOT_STICKY;
}
// the reason for the separation betweyeen SYNC_NOW and UPDATE_ALL is so that starting
// the sync directly from the notification is possible while the screen is on with
@@ -92,44 +97,47 @@ public class KeyserverSyncAdapterService extends Service {
Constants.PROVIDER_AUTHORITY,
new Bundle()
);
- break;
+ return START_NOT_STICKY;
}
case ACTION_UPDATE_ALL: {
// does not check for screen on/off
- asyncKeyUpdate(this, new CryptoInputParcel());
- break;
+ asyncKeyUpdate(this, new CryptoInputParcel(), startId);
+ // we depend on handleUpdateResult to call stopSelf when it is no longer necessary
+ // for the intent to be redelivered
+ return START_REDELIVER_INTENT;
}
case ACTION_IGNORE_TOR: {
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.cancel(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT);
- asyncKeyUpdate(this, new CryptoInputParcel(ParcelableProxy.getForNoProxy()));
- break;
+ asyncKeyUpdate(this, new CryptoInputParcel(ParcelableProxy.getForNoProxy()),
+ startId);
+ // we depend on handleUpdateResult to call stopSelf when it is no longer necessary
+ // for the intent to be redelivered
+ return START_REDELIVER_INTENT;
}
case ACTION_START_ORBOT: {
- NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ NotificationManager manager = (NotificationManager)
+ getSystemService(NOTIFICATION_SERVICE);
manager.cancel(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT);
+
Intent startOrbot = new Intent(this, OrbotRequiredDialogActivity.class);
startOrbot.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startOrbot.putExtra(OrbotRequiredDialogActivity.EXTRA_START_ORBOT, true);
+
Messenger messenger = new Messenger(
new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case OrbotRequiredDialogActivity.MESSAGE_ORBOT_STARTED: {
- asyncKeyUpdate(KeyserverSyncAdapterService.this,
- new CryptoInputParcel());
- break;
- }
- case OrbotRequiredDialogActivity.MESSAGE_ORBOT_IGNORE: {
- asyncKeyUpdate(KeyserverSyncAdapterService.this,
- new CryptoInputParcel(
- ParcelableProxy.getForNoProxy()));
+ startServiceWithUpdateAll();
break;
}
+ case OrbotRequiredDialogActivity.MESSAGE_ORBOT_IGNORE:
case OrbotRequiredDialogActivity.MESSAGE_DIALOG_CANCEL: {
- // just stop service
- stopSelf();
+ // not possible since we proceed to Orbot's Activity
+ // directly, by starting OrbotRequiredDialogActivity with
+ // EXTRA_START_ORBOT set to true
break;
}
}
@@ -138,13 +146,17 @@ public class KeyserverSyncAdapterService extends Service {
);
startOrbot.putExtra(OrbotRequiredDialogActivity.EXTRA_MESSENGER, messenger);
startActivity(startOrbot);
- break;
+ // since we return START_NOT_STICKY, we also postpone the sync as a backup in case
+ // the service is killed before OrbotRequiredDialogActivity can get back to us
+ postponeSync();
+ // if use START_REDELIVER_INTENT, we might annoy the user by repeatedly starting the
+ // Orbot Activity when our service is killed and restarted
+ return START_NOT_STICKY;
}
case ACTION_DISMISS_NOTIFICATION: {
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.cancel(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT);
- stopSelf(startId);
- break;
+ return START_NOT_STICKY;
}
}
return START_NOT_STICKY;
@@ -167,10 +179,7 @@ public class KeyserverSyncAdapterService extends Service {
boolean isScreenOn = pm.isScreenOn();
if (!isScreenOn) {
- Intent serviceIntent = new Intent(KeyserverSyncAdapterService.this,
- KeyserverSyncAdapterService.class);
- serviceIntent.setAction(ACTION_UPDATE_ALL);
- startService(serviceIntent);
+ startServiceWithUpdateAll();
} else {
postponeSync();
}
@@ -188,16 +197,24 @@ public class KeyserverSyncAdapterService extends Service {
return new KeyserverSyncAdapter().getSyncAdapterBinder();
}
- private void handleUpdateResult(ImportKeyResult result) {
+ /**
+ * Since we're returning START_REDELIVER_INTENT in onStartCommand, we need to remember to call
+ * stopSelf(int) to prevent the Intent from being redelivered if our work is already done
+ *
+ * @param result result of keyserver sync
+ * @param startId startId provided to the onStartCommand call which resulted in this sync
+ */
+ private void handleUpdateResult(ImportKeyResult result, final int startId) {
if (result.isPending()) {
+ Log.d(Constants.TAG, "Orbot required for sync but not running, attempting to start");
// result is pending due to Orbot not being started
// try to start it silently, if disabled show notifications
new OrbotHelper.SilentStartManager() {
@Override
protected void onOrbotStarted() {
// retry the update
- asyncKeyUpdate(KeyserverSyncAdapterService.this,
- new CryptoInputParcel());
+ startServiceWithUpdateAll();
+ stopSelf(startId); // startServiceWithUpdateAll will deliver a new Intent
}
@Override
@@ -207,16 +224,24 @@ public class KeyserverSyncAdapterService extends Service {
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.notify(Constants.Notification.KEYSERVER_SYNC_FAIL_ORBOT,
getOrbotNoification(KeyserverSyncAdapterService.this));
+ // further action on user interaction with notification, intent should not be
+ // redelivered, therefore:
+ stopSelf(startId);
}
}.startOrbotAndListen(this, false);
+ // if we're killed before we get a response from Orbot, we need the intent to be
+ // redelivered, so no stopSelf(int) here
} else if (isUpdateCancelled()) {
Log.d(Constants.TAG, "Keyserver sync cancelled, postponing by" + SYNC_POSTPONE_TIME
+ "ms");
postponeSync();
+ // postponeSync creates a new intent, so we don't need this to be redelivered
+ stopSelf(startId);
} else {
Log.d(Constants.TAG, "Keyserver sync completed: Updated: " + result.mUpdatedKeys
+ " Failed: " + result.mBadKeys);
- stopSelf();
+ // key sync completed successfully, we can stop
+ stopSelf(startId);
}
}
@@ -234,12 +259,12 @@ public class KeyserverSyncAdapterService extends Service {
}
private void asyncKeyUpdate(final Context context,
- final CryptoInputParcel cryptoInputParcel) {
+ final CryptoInputParcel cryptoInputParcel, final int startId) {
new Thread(new Runnable() {
@Override
public void run() {
ImportKeyResult result = updateKeysFromKeyserver(context, cryptoInputParcel);
- handleUpdateResult(result);
+ handleUpdateResult(result, startId);
}
}).start();
}
@@ -278,7 +303,6 @@ public class KeyserverSyncAdapterService extends Service {
);
}
-
/**
* will perform a staggered update of user's keys using delays to ensure new Tor circuits, as
* performed by parcimonie. Relevant issue and method at:
@@ -290,17 +314,31 @@ public class KeyserverSyncAdapterService extends Service {
CryptoInputParcel cryptoInputParcel) {
Log.d(Constants.TAG, "Starting staggered update");
// final int WEEK_IN_SECONDS = (int) TimeUnit.DAYS.toSeconds(7);
+ // we are limiting our randomness to ORBOT_CIRCUIT_TIMEOUT_SECONDS for now
final int WEEK_IN_SECONDS = 0;
+
ImportOperation.KeyImportAccumulator accumulator
= new ImportOperation.KeyImportAccumulator(keyList.size(), null);
+
+ // so that the first key can be updated without waiting. This is so that there isn't a
+ // large gap between a "Start Orbot" notification and the next key update
+ boolean first = true;
+
for (ParcelableKeyRing keyRing : keyList) {
int waitTime;
int staggeredTime = new Random().nextInt(1 + 2 * (WEEK_IN_SECONDS / keyList.size()));
- if (staggeredTime >= ORBOT_CIRCUIT_TIMEOUT) {
+ if (staggeredTime >= ORBOT_CIRCUIT_TIMEOUT_SECONDS) {
waitTime = staggeredTime;
} else {
- waitTime = ORBOT_CIRCUIT_TIMEOUT + new Random().nextInt(ORBOT_CIRCUIT_TIMEOUT);
+ waitTime = ORBOT_CIRCUIT_TIMEOUT_SECONDS
+ + new Random().nextInt(1 + ORBOT_CIRCUIT_TIMEOUT_SECONDS);
+ }
+
+ if (first) {
+ waitTime = 0;
+ first = false;
}
+
Log.d(Constants.TAG, "Updating key with fingerprint " + keyRing.mExpectedFingerprint +
" with a wait time of " + waitTime + "s");
try {
@@ -362,13 +400,15 @@ public class KeyserverSyncAdapterService extends Service {
);
ArrayList<Long> ignoreMasterKeyIds = new ArrayList<>();
- while (updatedKeysCursor.moveToNext()) {
+ while (updatedKeysCursor != null && updatedKeysCursor.moveToNext()) {
long masterKeyId = updatedKeysCursor.getLong(INDEX_UPDATED_KEYS_MASTER_KEY_ID);
Log.d(Constants.TAG, "Keyserver sync: Ignoring {" + masterKeyId + "} last updated at {"
+ updatedKeysCursor.getLong(INDEX_LAST_UPDATED) + "}s");
ignoreMasterKeyIds.add(masterKeyId);
}
- updatedKeysCursor.close();
+ if (updatedKeysCursor != null) {
+ updatedKeysCursor.close();
+ }
// 2. Make a list of public keys which should be updated
final int INDEX_MASTER_KEY_ID = 0;
@@ -413,7 +453,7 @@ public class KeyserverSyncAdapterService extends Service {
/**
* will cancel an update already in progress. We send an Intent to cancel it instead of simply
- * modifying a static variable sync the service is running in a process that is different from
+ * modifying a static variable since the service is running in a process that is different from
* the default application process where the UI code runs.
*
* @param context used to send an Intent to the service requesting cancellation.
@@ -491,6 +531,12 @@ public class KeyserverSyncAdapterService extends Service {
}
}
+ private void startServiceWithUpdateAll() {
+ Intent serviceIntent = new Intent(this, KeyserverSyncAdapterService.class);
+ serviceIntent.setAction(ACTION_UPDATE_ALL);
+ this.startService(serviceIntent);
+ }
+
// from de.azapps.mirakel.helper.Helpers from https://github.com/MirakelX/mirakel-android
private Bitmap getBitmap(int resId, Context context) {
int mLargeIconWidth = (int) context.getResources().getDimension(
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 5d04317b3..df29a388f 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java
@@ -256,14 +256,6 @@ public class PassphraseCacheService extends Service {
SecretKeyType keyType = keyRing.getSecretKeyType(subKeyId);
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/
- } else {
- Log.d(Constants.TAG, "PassphraseCacheService: NOT using default YubiKey PIN");
- break;
- }
case PASSPHRASE_EMPTY:
return new Passphrase("");
case UNAVAILABLE:
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/UploadKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/UploadKeyringParcel.java
new file mode 100644
index 000000000..0a14f3dc6
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/UploadKeyringParcel.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
+ * Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.service;
+
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+
+public class UploadKeyringParcel implements Parcelable {
+ public String mKeyserver;
+
+ public final Long mMasterKeyId;
+ public final byte[] mUncachedKeyringBytes;
+
+ public UploadKeyringParcel(String keyserver, long masterKeyId) {
+ mKeyserver = keyserver;
+ mMasterKeyId = masterKeyId;
+ mUncachedKeyringBytes = null;
+ }
+
+ public UploadKeyringParcel(String keyserver, byte[] uncachedKeyringBytes) {
+ mKeyserver = keyserver;
+ mMasterKeyId = null;
+ mUncachedKeyringBytes = uncachedKeyringBytes;
+ }
+
+ protected UploadKeyringParcel(Parcel in) {
+ mKeyserver = in.readString();
+ mMasterKeyId = in.readInt() != 0 ? in.readLong() : null;
+ mUncachedKeyringBytes = in.createByteArray();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mKeyserver);
+ if (mMasterKeyId != null) {
+ dest.writeInt(1);
+ dest.writeLong(mMasterKeyId);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeByteArray(mUncachedKeyringBytes);
+ }
+
+ public static final Creator<UploadKeyringParcel> CREATOR = new Creator<UploadKeyringParcel>() {
+ @Override
+ public UploadKeyringParcel createFromParcel(Parcel in) {
+ return new UploadKeyringParcel(in);
+ }
+
+ @Override
+ public UploadKeyringParcel[] newArray(int size) {
+ return new UploadKeyringParcel[size];
+ }
+ };
+} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java
index 0d8569fe6..849418905 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/CryptoInputParcel.java
@@ -23,6 +23,7 @@ import android.os.Parcelable;
import org.sufficientlysecure.keychain.util.ParcelableProxy;
import org.sufficientlysecure.keychain.util.Passphrase;
+import java.net.Proxy;
import java.nio.ByteBuffer;
import java.util.Date;
import java.util.HashMap;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java
index e4dac3227..1f99836ea 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java
@@ -14,8 +14,8 @@ import java.util.Date;
public class RequiredInputParcel implements Parcelable {
public enum RequiredInputType {
- PASSPHRASE, PASSPHRASE_SYMMETRIC, NFC_SIGN, NFC_DECRYPT, NFC_MOVE_KEY_TO_CARD, ENABLE_ORBOT,
- UPLOAD_FAIL_RETRY
+ PASSPHRASE, PASSPHRASE_SYMMETRIC, BACKUP_CODE, NFC_SIGN, NFC_DECRYPT,
+ NFC_MOVE_KEY_TO_CARD, NFC_RESET_CARD, ENABLE_ORBOT, UPLOAD_FAIL_RETRY,
}
public Date mSignatureTime;
@@ -100,6 +100,11 @@ public class RequiredInputParcel implements Parcelable {
new byte[][] { encryptedSessionKey }, null, null, masterKeyId, subKeyId);
}
+ public static RequiredInputParcel createNfcReset() {
+ return new RequiredInputParcel(RequiredInputType.NFC_RESET_CARD,
+ null, null, null, null, null);
+ }
+
public static RequiredInputParcel createRequiredSignPassphrase(
long masterKeyId, long subKeyId, Date signatureTime) {
return new RequiredInputParcel(RequiredInputType.PASSPHRASE,
@@ -117,6 +122,11 @@ public class RequiredInputParcel implements Parcelable {
null, null, null, null, null);
}
+ public static RequiredInputParcel createRequiredBackupCode() {
+ return new RequiredInputParcel(RequiredInputType.BACKUP_CODE,
+ null, null, null, null, null);
+ }
+
public static RequiredInputParcel createRequiredPassphrase(
RequiredInputParcel req) {
return new RequiredInputParcel(RequiredInputType.PASSPHRASE,
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupActivity.java
new file mode 100644
index 000000000..ff120c9b5
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupActivity.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.sufficientlysecure.keychain.ui;
+
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.view.MenuItem;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.ui.base.BaseActivity;
+
+
+public class BackupActivity extends BaseActivity {
+
+ public static final String EXTRA_MASTER_KEY_IDS = "master_key_ids";
+ public static final String EXTRA_SECRET = "export_secret";
+
+ @Override
+ protected void initLayout() {
+ setContentView(R.layout.backup_activity);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // noinspection ConstantConditions, we know this activity has an action bar
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+
+ if (savedInstanceState == null) {
+ Intent intent = getIntent();
+ boolean exportSecret = intent.getBooleanExtra(EXTRA_SECRET, false);
+ long[] masterKeyIds = intent.getLongArrayExtra(EXTRA_MASTER_KEY_IDS);
+
+ Fragment frag = BackupCodeFragment.newInstance(masterKeyIds, exportSecret);
+
+ FragmentManager fragMan = getSupportFragmentManager();
+ fragMan.beginTransaction()
+ .setCustomAnimations(0, 0)
+ .replace(R.id.content_frame, frag)
+ .commit();
+ }
+
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ FragmentManager fragMan = getSupportFragmentManager();
+ // pop from back stack, or if nothing was on there finish activity
+ if ( ! fragMan.popBackStackImmediate()) {
+ finish();
+ }
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java
new file mode 100644
index 000000000..8afdb5f94
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java
@@ -0,0 +1,543 @@
+/*
+ * 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.io.File;
+import java.io.IOException;
+import java.security.SecureRandom;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Random;
+
+import android.animation.ArgbEvaluator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentManager.OnBackStackChangedListener;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateInterpolator;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.operations.results.ExportResult;
+import org.sufficientlysecure.keychain.provider.TemporaryFileProvider;
+import org.sufficientlysecure.keychain.service.BackupKeyringParcel;
+import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
+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.widget.ToolableViewAnimator;
+import org.sufficientlysecure.keychain.util.FileHelper;
+import org.sufficientlysecure.keychain.util.Passphrase;
+
+public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringParcel, ExportResult>
+ implements OnBackStackChangedListener {
+
+ public static final String ARG_BACKUP_CODE = "backup_code";
+ public static final String BACK_STACK_INPUT = "state_display";
+ public static final String ARG_EXPORT_SECRET = "export_secret";
+ public static final String ARG_MASTER_KEY_IDS = "master_key_ids";
+
+ public static final String ARG_CURRENT_STATE = "current_state";
+
+ public static final int REQUEST_SAVE = 1;
+ public static final String ARG_BACK_STACK = "back_stack";
+
+ // argument variables
+ private boolean mExportSecret;
+ private long[] mMasterKeyIds;
+ String mBackupCode;
+
+ private EditText[] mCodeEditText;
+ private ToolableViewAnimator mStatusAnimator, mTitleAnimator, mCodeFieldsAnimator;
+
+ private Integer mBackStackLevel;
+
+ private Uri mCachedBackupUri;
+ private boolean mShareNotSave;
+
+ public static BackupCodeFragment newInstance(long[] masterKeyIds, boolean exportSecret) {
+ BackupCodeFragment frag = new BackupCodeFragment();
+
+ Bundle args = new Bundle();
+ args.putString(ARG_BACKUP_CODE, generateRandomCode());
+ args.putLongArray(ARG_MASTER_KEY_IDS, masterKeyIds);
+ args.putBoolean(ARG_EXPORT_SECRET, exportSecret);
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ enum BackupCodeState {
+ STATE_UNINITIALIZED, STATE_DISPLAY, STATE_INPUT, STATE_INPUT_ERROR, STATE_OK
+ }
+
+ BackupCodeState mCurrentState = BackupCodeState.STATE_UNINITIALIZED;
+
+ void switchState(BackupCodeState state, boolean animate) {
+
+ switch (state) {
+ case STATE_UNINITIALIZED:
+ throw new AssertionError("can't switch to uninitialized state, this is a bug!");
+
+ case STATE_DISPLAY:
+ mTitleAnimator.setDisplayedChild(0, animate);
+ mStatusAnimator.setDisplayedChild(0, animate);
+ mCodeFieldsAnimator.setDisplayedChild(0, animate);
+
+ break;
+
+ case STATE_INPUT:
+ mTitleAnimator.setDisplayedChild(1, animate);
+ mStatusAnimator.setDisplayedChild(1, animate);
+ mCodeFieldsAnimator.setDisplayedChild(1, animate);
+
+ for (EditText editText : mCodeEditText) {
+ editText.setText("");
+ }
+
+ pushBackStackEntry();
+
+ break;
+
+ case STATE_INPUT_ERROR: {
+ mTitleAnimator.setDisplayedChild(1, false);
+ mStatusAnimator.setDisplayedChild(2, animate);
+ mCodeFieldsAnimator.setDisplayedChild(1, false);
+
+ hideKeyboard();
+
+ if (animate) {
+ @ColorInt int black = mCodeEditText[0].getCurrentTextColor();
+ @ColorInt int red = getResources().getColor(R.color.android_red_dark);
+ animateFlashText(mCodeEditText, black, red, false);
+ }
+
+ break;
+ }
+
+ case STATE_OK: {
+ mTitleAnimator.setDisplayedChild(2, animate);
+ mStatusAnimator.setDisplayedChild(3, animate);
+ mCodeFieldsAnimator.setDisplayedChild(1, false);
+
+ hideKeyboard();
+
+ for (EditText editText : mCodeEditText) {
+ editText.setEnabled(false);
+ }
+
+ @ColorInt int green = getResources().getColor(R.color.android_green_dark);
+ if (animate) {
+ @ColorInt int black = mCodeEditText[0].getCurrentTextColor();
+ animateFlashText(mCodeEditText, black, green, true);
+ } else {
+ for (TextView textView : mCodeEditText) {
+ textView.setTextColor(green);
+ }
+ }
+
+ popBackStackNoAction();
+
+ break;
+ }
+
+ }
+
+ mCurrentState = state;
+
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.backup_code_fragment, container, false);
+
+ Bundle args = getArguments();
+ mBackupCode = args.getString(ARG_BACKUP_CODE);
+ mMasterKeyIds = args.getLongArray(ARG_MASTER_KEY_IDS);
+ mExportSecret = args.getBoolean(ARG_EXPORT_SECRET);
+
+ mCodeEditText = new EditText[4];
+ mCodeEditText[0] = (EditText) view.findViewById(R.id.backup_code_1);
+ mCodeEditText[1] = (EditText) view.findViewById(R.id.backup_code_2);
+ mCodeEditText[2] = (EditText) view.findViewById(R.id.backup_code_3);
+ mCodeEditText[3] = (EditText) view.findViewById(R.id.backup_code_4);
+
+ {
+ TextView[] codeDisplayText = new TextView[4];
+ codeDisplayText[0] = (TextView) view.findViewById(R.id.backup_code_display_1);
+ codeDisplayText[1] = (TextView) view.findViewById(R.id.backup_code_display_2);
+ codeDisplayText[2] = (TextView) view.findViewById(R.id.backup_code_display_3);
+ codeDisplayText[3] = (TextView) view.findViewById(R.id.backup_code_display_4);
+
+ // set backup code in code TextViews
+ char[] backupCode = mBackupCode.toCharArray();
+ for (int i = 0; i < codeDisplayText.length; i++) {
+ codeDisplayText[i].setText(backupCode, i * 7, 6);
+ }
+
+ // set background to null in TextViews - this will retain padding from EditText style!
+ for (TextView textView : codeDisplayText) {
+ // noinspection deprecation, setBackground(Drawable) is API level >=16
+ textView.setBackgroundDrawable(null);
+ }
+ }
+
+ setupEditTextFocusNext(mCodeEditText);
+ setupEditTextSuccessListener(mCodeEditText);
+
+ mStatusAnimator = (ToolableViewAnimator) view.findViewById(R.id.status_animator);
+ mTitleAnimator = (ToolableViewAnimator) view.findViewById(R.id.title_animator);
+ mCodeFieldsAnimator = (ToolableViewAnimator) view.findViewById(R.id.code_animator);
+
+ View backupInput = view.findViewById(R.id.button_backup_input);
+ backupInput.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ switchState(BackupCodeState.STATE_INPUT, true);
+ }
+ });
+
+ view.findViewById(R.id.button_backup_save).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mShareNotSave = false;
+ startBackup();
+ }
+ });
+
+ view.findViewById(R.id.button_backup_share).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mShareNotSave = true;
+ startBackup();
+ }
+ });
+
+ view.findViewById(R.id.button_backup_back).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ FragmentManager fragMan = getFragmentManager();
+ if (fragMan != null) {
+ fragMan.popBackStack();
+ }
+ }
+ });
+
+ return view;
+ }
+
+ @Override
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ if (savedInstanceState != null) {
+ int savedBackStack = savedInstanceState.getInt(ARG_BACK_STACK);
+ if (savedBackStack >= 0) {
+ mBackStackLevel = savedBackStack;
+ // unchecked use, we know that this one is available in onViewCreated
+ getFragmentManager().addOnBackStackChangedListener(this);
+ }
+ BackupCodeState savedState = BackupCodeState.values()[savedInstanceState.getInt(ARG_CURRENT_STATE)];
+ switchState(savedState, false);
+ } else if (mCurrentState == BackupCodeState.STATE_UNINITIALIZED) {
+ switchState(BackupCodeState.STATE_DISPLAY, true);
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(ARG_CURRENT_STATE, mCurrentState.ordinal());
+ outState.putInt(ARG_BACK_STACK, mBackStackLevel == null ? -1 : mBackStackLevel);
+ }
+
+ private void setupEditTextSuccessListener(final EditText[] backupCodes) {
+ for (EditText backupCode : backupCodes) {
+
+ backupCode.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ if (s.length() > 6) {
+ throw new AssertionError("max length of each field is 6!");
+ }
+
+ boolean inInputState = mCurrentState == BackupCodeState.STATE_INPUT
+ || mCurrentState == BackupCodeState.STATE_INPUT_ERROR;
+ boolean partIsComplete = s.length() == 6;
+ if (!inInputState || !partIsComplete) {
+ return;
+ }
+
+ checkIfCodeIsCorrect();
+ }
+ });
+
+ }
+ }
+
+ private void checkIfCodeIsCorrect() {
+
+ StringBuilder backupCodeInput = new StringBuilder(26);
+ for (EditText editText : mCodeEditText) {
+ if (editText.getText().length() < 6) {
+ return;
+ }
+ backupCodeInput.append(editText.getText());
+ backupCodeInput.append('-');
+ }
+ backupCodeInput.deleteCharAt(backupCodeInput.length() - 1);
+
+ // if they don't match, do nothing
+ if (backupCodeInput.toString().equals(mBackupCode)) {
+ switchState(BackupCodeState.STATE_OK, true);
+ return;
+ }
+
+ switchState(BackupCodeState.STATE_INPUT_ERROR, true);
+
+ }
+
+ private static void animateFlashText(
+ final TextView[] textViews, int color1, int color2, boolean staySecondColor) {
+
+ ValueAnimator anim = ValueAnimator.ofObject(new ArgbEvaluator(), color1, color2);
+ anim.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animator) {
+ for (TextView textView : textViews) {
+ textView.setTextColor((Integer) animator.getAnimatedValue());
+ }
+ }
+ });
+ anim.setRepeatMode(ValueAnimator.REVERSE);
+ anim.setRepeatCount(staySecondColor ? 4 : 5);
+ anim.setDuration(180);
+ anim.setInterpolator(new AccelerateInterpolator());
+ anim.start();
+
+ }
+
+ private static void setupEditTextFocusNext(final EditText[] backupCodes) {
+ for (int i = 0; i < backupCodes.length - 1; i++) {
+
+ final int next = i + 1;
+
+ backupCodes[i].addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ boolean inserting = before < count;
+ boolean cursorAtEnd = (start + count) == 6;
+
+ if (inserting && cursorAtEnd) {
+ backupCodes[next].requestFocus();
+ }
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ }
+ });
+
+ }
+ }
+
+ private void pushBackStackEntry() {
+ if (mBackStackLevel != null) {
+ return;
+ }
+ FragmentManager fragMan = getFragmentManager();
+ mBackStackLevel = fragMan.getBackStackEntryCount();
+ fragMan.beginTransaction().addToBackStack(BACK_STACK_INPUT).commit();
+ fragMan.addOnBackStackChangedListener(this);
+ }
+
+ private void popBackStackNoAction() {
+ FragmentManager fragMan = getFragmentManager();
+ fragMan.removeOnBackStackChangedListener(this);
+ fragMan.popBackStackImmediate(BACK_STACK_INPUT, FragmentManager.POP_BACK_STACK_INCLUSIVE);
+ mBackStackLevel = null;
+ }
+
+ @Override
+ public void onBackStackChanged() {
+ FragmentManager fragMan = getFragmentManager();
+ if (mBackStackLevel != null && fragMan.getBackStackEntryCount() == mBackStackLevel) {
+ fragMan.removeOnBackStackChangedListener(this);
+ switchState(BackupCodeState.STATE_DISPLAY, true);
+ mBackStackLevel = null;
+ }
+ }
+
+ private void startBackup() {
+
+ FragmentActivity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
+
+ String date = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date());
+ String filename = Constants.FILE_ENCRYPTED_BACKUP_PREFIX + date
+ + (mExportSecret ? Constants.FILE_EXTENSION_ENCRYPTED_BACKUP_SECRET
+ : Constants.FILE_EXTENSION_ENCRYPTED_BACKUP_PUBLIC);
+
+ if (mCachedBackupUri == null) {
+ mCachedBackupUri = TemporaryFileProvider.createFile(activity, filename,
+ Constants.MIME_TYPE_ENCRYPTED_ALTERNATE);
+ cryptoOperation();
+ return;
+ }
+
+ if (mShareNotSave) {
+ Intent intent = new Intent(Intent.ACTION_SEND);
+ intent.setType(Constants.MIME_TYPE_ENCRYPTED_ALTERNATE);
+ intent.putExtra(Intent.EXTRA_STREAM, mCachedBackupUri);
+ startActivity(intent);
+ } else {
+ saveFile(filename, false);
+ }
+
+ }
+
+ private void saveFile(final String filename, boolean overwrite) {
+ FragmentActivity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
+
+ // for kitkat and above, we have the document api
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ FileHelper.saveDocument(this, filename, Constants.MIME_TYPE_ENCRYPTED_ALTERNATE, REQUEST_SAVE);
+ return;
+ }
+
+ File file = new File(Constants.Path.APP_DIR, filename);
+
+ if (!overwrite && file.exists()) {
+ Notify.create(activity, R.string.snack_backup_exists, Style.WARN, new ActionListener() {
+ @Override
+ public void onAction() {
+ saveFile(filename, true);
+ }
+ }, R.string.snack_btn_overwrite).show();
+ return;
+ }
+
+ try {
+ FileHelper.copyUriData(activity, mCachedBackupUri, Uri.fromFile(file));
+ Notify.create(activity, R.string.snack_backup_saved_dir, Style.OK).show();
+ } catch (IOException e) {
+ Notify.create(activity, R.string.snack_backup_error_saving, Style.ERROR).show();
+ }
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode != REQUEST_SAVE) {
+ super.onActivityResult(requestCode, resultCode, data);
+ return;
+ }
+
+ if (resultCode != FragmentActivity.RESULT_OK) {
+ return;
+ }
+
+ FragmentActivity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
+ try {
+ Uri outputUri = data.getData();
+ FileHelper.copyUriData(activity, mCachedBackupUri, outputUri);
+ Notify.create(activity, R.string.snack_backup_saved, Style.OK).show();
+ } catch (IOException e) {
+ Notify.create(activity, R.string.snack_backup_error_saving, Style.ERROR).show();
+ }
+ }
+
+ @Nullable
+ @Override
+ public BackupKeyringParcel createOperationInput() {
+ return new BackupKeyringParcel(new Passphrase(mBackupCode), mMasterKeyIds, mExportSecret, mCachedBackupUri);
+ }
+
+ @Override
+ public void onCryptoOperationSuccess(ExportResult result) {
+ startBackup();
+ }
+
+ @Override
+ public void onCryptoOperationError(ExportResult result) {
+ result.createNotify(getActivity()).show();
+ mCachedBackupUri = null;
+ }
+
+ @Override
+ public void onCryptoOperationCancelled() {
+ mCachedBackupUri = null;
+ }
+
+ @NonNull
+ private static String generateRandomCode() {
+
+ Random r = new SecureRandom();
+
+ // simple generation of a 20 character backup code
+ StringBuilder code = new StringBuilder(28);
+ for (int i = 0; i < 24; i++) {
+ if (i == 6 || i == 12 || i == 18) {
+ code.append('-');
+ }
+ code.append((char) ('A' + r.nextInt(26)));
+ }
+
+ return code.toString();
+
+ }
+
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupRestoreFragment.java
index a3ea8ad9a..25601d655 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupRestoreFragment.java
@@ -17,17 +17,13 @@
package org.sufficientlysecure.keychain.ui;
-
-import java.io.File;
-import java.text.SimpleDateFormat;
import java.util.ArrayList;
-import java.util.Date;
-import java.util.Locale;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.Intent;
import android.database.Cursor;
+import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.Nullable;
@@ -37,41 +33,29 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
-import org.sufficientlysecure.keychain.util.ExportHelper;
+import org.sufficientlysecure.keychain.ui.util.Notify;
+import org.sufficientlysecure.keychain.util.FileHelper;
-public class BackupFragment extends Fragment {
+public class BackupRestoreFragment extends Fragment {
// This ids for multiple key export.
private ArrayList<Long> mIdsForRepeatAskPassphrase;
// This index for remembering the number of master key.
private int mIndex;
- static final int REQUEST_REPEAT_PASSPHRASE = 1;
- private ExportHelper mExportHelper;
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- // we won't get attached to a non-fragment activity, so the cast should be safe
- mExportHelper = new ExportHelper((FragmentActivity) activity);
- }
-
- @Override
- public void onDetach() {
- super.onDetach();
- mExportHelper = null;
- }
+ private static final int REQUEST_REPEAT_PASSPHRASE = 0x00007002;
+ private static final int REQUEST_CODE_INPUT = 0x00007003;
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.backup_fragment, container, false);
+ View view = inflater.inflate(R.layout.backup_restore_fragment, container, false);
View backupAll = view.findViewById(R.id.backup_all);
View backupPublicKeys = view.findViewById(R.id.backup_public_keys);
+ final View restore = view.findViewById(R.id.restore);
backupAll.setOnClickListener(new View.OnClickListener() {
@Override
@@ -87,6 +71,13 @@ public class BackupFragment extends Fragment {
}
});
+ restore.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ restore();
+ }
+ });
+
return view;
}
@@ -101,12 +92,12 @@ public class BackupFragment extends Fragment {
return;
}
- new AsyncTask<ContentResolver,Void,ArrayList<Long>>() {
+ new AsyncTask<ContentResolver, Void, ArrayList<Long>>() {
@Override
protected ArrayList<Long> doInBackground(ContentResolver... resolver) {
ArrayList<Long> askPassphraseIds = new ArrayList<>();
Cursor cursor = resolver[0].query(
- KeyRings.buildUnifiedKeyRingsUri(), new String[] {
+ KeyRings.buildUnifiedKeyRingsUri(), new String[]{
KeyRings.MASTER_KEY_ID,
KeyRings.HAS_SECRET,
}, KeyRings.HAS_SECRET + " != 0", null, null);
@@ -156,7 +147,6 @@ public class BackupFragment extends Fragment {
}
}.execute(activity.getContentResolver());
-
}
private void startPassphraseActivity() {
@@ -173,28 +163,53 @@ public class BackupFragment extends Fragment {
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (requestCode == REQUEST_REPEAT_PASSPHRASE) {
- if (resultCode != Activity.RESULT_OK) {
- return;
+ switch (requestCode) {
+ case REQUEST_REPEAT_PASSPHRASE: {
+ if (resultCode != Activity.RESULT_OK) {
+ return;
+ }
+ if (mIndex < mIdsForRepeatAskPassphrase.size()) {
+ startPassphraseActivity();
+ return;
+ }
+
+ startBackup(true);
+
+ break;
}
- if (mIndex < mIdsForRepeatAskPassphrase.size()) {
- startPassphraseActivity();
- return;
+
+ case REQUEST_CODE_INPUT: {
+ if (resultCode != Activity.RESULT_OK || data == null) {
+ return;
+ }
+
+ Uri uri = data.getData();
+ if (uri == null) {
+ Notify.create(getActivity(), R.string.no_file_selected, Notify.Style.ERROR).show();
+ return;
+ }
+
+ Intent intent = new Intent(getActivity(), DecryptActivity.class);
+ intent.setAction(Intent.ACTION_VIEW);
+ intent.setData(uri);
+ startActivity(intent);
+ break;
}
- startBackup(true);
+ default: {
+ super.onActivityResult(requestCode, resultCode, data);
+ }
}
}
private void startBackup(boolean exportSecret) {
- File filename;
- String date = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date());
- if (exportSecret) {
- filename = new File(Constants.Path.APP_DIR, "keys_" + date + ".asc");
- } else {
- filename = new File(Constants.Path.APP_DIR, "keys_" + date + ".pub.asc");
- }
- mExportHelper.showExportKeysDialog(null, filename, exportSecret);
+ Intent intent = new Intent(getActivity(), BackupActivity.class);
+ intent.putExtra(BackupActivity.EXTRA_SECRET, exportSecret);
+ startActivity(intent);
+ }
+
+ private void restore() {
+ FileHelper.openDocument(this, null, "*/*", false, REQUEST_CODE_INPUT);
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintFragment.java
index 552fa34c0..85be68505 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyFingerprintFragment.java
@@ -33,12 +33,14 @@ import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
+import org.sufficientlysecure.keychain.experimental.SentenceConfirm;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
-import org.sufficientlysecure.keychain.ui.util.ExperimentalWordConfirm;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Log;
+import java.io.IOException;
+
public class CertifyFingerprintFragment extends LoaderFragment implements
LoaderManager.LoaderCallbacks<Cursor> {
@@ -46,24 +48,26 @@ public class CertifyFingerprintFragment extends LoaderFragment implements
static final int REQUEST_CERTIFY = 1;
public static final String ARG_DATA_URI = "uri";
- public static final String ARG_ENABLE_WORD_CONFIRM = "enable_word_confirm";
+ public static final String ARG_ENABLE_PHRASES_CONFIRM = "enable_word_confirm";
+ private TextView mActionYes;
private TextView mFingerprint;
private TextView mIntro;
+ private TextView mHeader;
private static final int LOADER_ID_UNIFIED = 0;
private Uri mDataUri;
- private boolean mEnableWordConfirm;
+ private boolean mEnablePhrasesConfirm;
/**
* Creates new instance of this fragment
*/
- public static CertifyFingerprintFragment newInstance(Uri dataUri, boolean enableWordConfirm) {
+ public static CertifyFingerprintFragment newInstance(Uri dataUri, boolean enablePhrasesConfirm) {
CertifyFingerprintFragment frag = new CertifyFingerprintFragment();
Bundle args = new Bundle();
args.putParcelable(ARG_DATA_URI, dataUri);
- args.putBoolean(ARG_ENABLE_WORD_CONFIRM, enableWordConfirm);
+ args.putBoolean(ARG_ENABLE_PHRASES_CONFIRM, enablePhrasesConfirm);
frag.setArguments(args);
@@ -75,11 +79,12 @@ public class CertifyFingerprintFragment extends LoaderFragment implements
View root = super.onCreateView(inflater, superContainer, savedInstanceState);
View view = inflater.inflate(R.layout.certify_fingerprint_fragment, getContainer());
- View actionNo = view.findViewById(R.id.certify_fingerprint_button_no);
- View actionYes = view.findViewById(R.id.certify_fingerprint_button_yes);
+ TextView actionNo = (TextView) view.findViewById(R.id.certify_fingerprint_button_no);
+ mActionYes = (TextView) view.findViewById(R.id.certify_fingerprint_button_yes);
mFingerprint = (TextView) view.findViewById(R.id.certify_fingerprint_fingerprint);
mIntro = (TextView) view.findViewById(R.id.certify_fingerprint_intro);
+ mHeader = (TextView) view.findViewById(R.id.certify_fingerprint_fingerprint_header);
actionNo.setOnClickListener(new View.OnClickListener() {
@Override
@@ -87,7 +92,7 @@ public class CertifyFingerprintFragment extends LoaderFragment implements
getActivity().finish();
}
});
- actionYes.setOnClickListener(new View.OnClickListener() {
+ mActionYes.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
certify(mDataUri);
@@ -107,10 +112,12 @@ public class CertifyFingerprintFragment extends LoaderFragment implements
getActivity().finish();
return;
}
- mEnableWordConfirm = getArguments().getBoolean(ARG_ENABLE_WORD_CONFIRM);
+ mEnablePhrasesConfirm = getArguments().getBoolean(ARG_ENABLE_PHRASES_CONFIRM);
- if (mEnableWordConfirm) {
- mIntro.setText(R.string.certify_fingerprint_text_words);
+ if (mEnablePhrasesConfirm) {
+ mIntro.setText(R.string.certify_fingerprint_text_phrases);
+ mHeader.setText(R.string.section_phrases);
+ mActionYes.setText(R.string.btn_match_phrases);
}
loadData(dataUri);
@@ -160,7 +167,7 @@ public class CertifyFingerprintFragment extends LoaderFragment implements
if (data.moveToFirst()) {
byte[] fingerprintBlob = data.getBlob(INDEX_UNIFIED_FINGERPRINT);
- if (mEnableWordConfirm) {
+ if (mEnablePhrasesConfirm) {
displayWordConfirm(fingerprintBlob);
} else {
displayHexConfirm(fingerprintBlob);
@@ -180,9 +187,17 @@ public class CertifyFingerprintFragment extends LoaderFragment implements
}
private void displayWordConfirm(byte[] fingerprintBlob) {
- String fingerprint = ExperimentalWordConfirm.getWords(getActivity(), fingerprintBlob);
+// String fingerprint = ExperimentalWordConfirm.getWords(getActivity(), fingerprintBlob);
+
+ String fingerprint;
+ try {
+ fingerprint = new SentenceConfirm(getActivity()).fromBytes(fingerprintBlob, 20);
+ } catch (IOException e) {
+ fingerprint = "-";
+ Log.e(Constants.TAG, "Problem when creating sentence!", e);
+ }
- mFingerprint.setTextSize(24);
+ mFingerprint.setTextSize(18);
mFingerprint.setTypeface(Typeface.DEFAULT, Typeface.BOLD);
mFingerprint.setText(fingerprint);
}
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 579a001cb..a4163d7f9 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java
@@ -30,7 +30,6 @@ 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.ui.util.Notify;
import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.Preferences;
@@ -114,19 +113,15 @@ public class CreateKeyActivity extends BaseNfcActivity {
byte[] nfcAid = intent.getByteArrayExtra(EXTRA_NFC_AID);
if (containsKeys(nfcFingerprints)) {
- Fragment frag = CreateYubiKeyImportFragment.newInstance(
+ Fragment frag = CreateYubiKeyImportResetFragment.newInstance(
nfcFingerprints, nfcAid, nfcUserId);
loadFragment(frag, FragAction.START);
setTitle(R.string.title_import_keys);
} else {
-// Fragment frag = CreateYubiKeyBlankFragment.newInstance();
-// loadFragment(frag, FragAction.START);
-// setTitle(R.string.title_manage_my_keys);
- Notify.create(this,
- "YubiKey key creation is currently not supported. Please follow our FAQ.",
- Notify.Style.ERROR
- ).show();
+ Fragment frag = CreateYubiKeyBlankFragment.newInstance();
+ loadFragment(frag, FragAction.START);
+ setTitle(R.string.title_manage_my_keys);
}
// done
@@ -160,7 +155,7 @@ public class CreateKeyActivity extends BaseNfcActivity {
}
@Override
- protected void onNfcPostExecute() throws IOException {
+ protected void onNfcPostExecute() {
if (mCurrentFragment instanceof NfcListenerFragment) {
((NfcListenerFragment) mCurrentFragment).onNfcPostExecute();
return;
@@ -181,30 +176,28 @@ public class CreateKeyActivity extends BaseNfcActivity {
finish();
} catch (PgpKeyNotFoundException e) {
- Fragment frag = CreateYubiKeyImportFragment.newInstance(
+ Fragment frag = CreateYubiKeyImportResetFragment.newInstance(
mScannedFingerprints, mNfcAid, mNfcUserId);
loadFragment(frag, FragAction.TO_RIGHT);
}
} else {
-// Fragment frag = CreateYubiKeyBlankFragment.newInstance();
-// loadFragment(frag, FragAction.TO_RIGHT);
- Notify.create(this,
- "YubiKey key creation is currently not supported. Please follow our FAQ.",
- Notify.Style.ERROR
- ).show();
+ Fragment frag = CreateYubiKeyBlankFragment.newInstance();
+ loadFragment(frag, FragAction.TO_RIGHT);
}
}
private boolean containsKeys(byte[] scannedFingerprints) {
+ if (scannedFingerprints == null) {
+ return false;
+ }
+
// If all fingerprint bytes are 0, the card contains no keys.
- boolean cardContainsKeys = false;
for (byte b : scannedFingerprints) {
if (b != 0) {
- cardContainsKeys = true;
- break;
+ return true;
}
}
- return cardContainsKeys;
+ return false;
}
@Override
@@ -264,7 +257,7 @@ public class CreateKeyActivity extends BaseNfcActivity {
interface NfcListenerFragment {
void doNfcInBackground() throws IOException;
- void onNfcPostExecute() throws IOException;
+ void onNfcPostExecute();
}
@Override
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java
index 739eb3e35..b79e4454d 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java
@@ -37,17 +37,17 @@ import org.spongycastle.bcpg.sig.KeyFlags;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
-import org.sufficientlysecure.keychain.operations.results.ExportResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
+import org.sufficientlysecure.keychain.operations.results.UploadResult;
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.ProviderHelper;
-import org.sufficientlysecure.keychain.service.ExportKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel;
+import org.sufficientlysecure.keychain.service.UploadKeyringParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
@@ -69,7 +69,7 @@ public class CreateKeyFinalFragment extends Fragment {
SaveKeyringParcel mSaveKeyringParcel;
- private CryptoOperationHelper<ExportKeyringParcel, ExportResult> mUploadOpHelper;
+ private CryptoOperationHelper<UploadKeyringParcel, UploadResult> mUploadOpHelper;
private CryptoOperationHelper<SaveKeyringParcel, EditKeyResult> mCreateOpHelper;
private CryptoOperationHelper<SaveKeyringParcel, EditKeyResult> mMoveToCardOpHelper;
@@ -407,20 +407,20 @@ public class CreateKeyFinalFragment extends Fragment {
}
// set data uri as path to keyring
- final Uri blobUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(saveKeyResult.mMasterKeyId);
+ final long masterKeyId = saveKeyResult.mMasterKeyId;
// upload to favorite keyserver
final String keyserver = Preferences.getPreferences(activity).getPreferredKeyserver();
- CryptoOperationHelper.Callback<ExportKeyringParcel, ExportResult> callback
- = new CryptoOperationHelper.Callback<ExportKeyringParcel, ExportResult>() {
+ CryptoOperationHelper.Callback<UploadKeyringParcel, UploadResult> callback
+ = new CryptoOperationHelper.Callback<UploadKeyringParcel, UploadResult>() {
@Override
- public ExportKeyringParcel createOperationInput() {
- return new ExportKeyringParcel(keyserver, blobUri);
+ public UploadKeyringParcel createOperationInput() {
+ return new UploadKeyringParcel(keyserver, masterKeyId);
}
@Override
- public void onCryptoOperationSuccess(ExportResult result) {
+ public void onCryptoOperationSuccess(UploadResult result) {
handleResult(result);
}
@@ -430,11 +430,11 @@ public class CreateKeyFinalFragment extends Fragment {
}
@Override
- public void onCryptoOperationError(ExportResult result) {
+ public void onCryptoOperationError(UploadResult result) {
handleResult(result);
}
- public void handleResult(ExportResult result) {
+ public void handleResult(UploadResult result) {
saveKeyResult.getLog().add(result, 0);
finishWithResult(saveKeyResult);
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyImportFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyImportResetFragment.java
index d88e6b9f9..0a2d52617 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyImportFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyImportResetFragment.java
@@ -25,11 +25,13 @@ import java.util.ArrayList;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
+import android.os.Parcelable;
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.CompoundButton;
+import android.widget.RadioButton;
import android.widget.TextView;
import org.spongycastle.util.encoders.Hex;
@@ -38,6 +40,8 @@ import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.NfcListenerFragment;
import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment;
@@ -45,11 +49,13 @@ import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Preferences;
-public class CreateYubiKeyImportFragment
+public class CreateYubiKeyImportResetFragment
extends QueueingCryptoOperationFragment<ImportKeyringParcel, ImportKeyResult>
implements NfcListenerFragment {
- private static final String ARG_FINGERPRINT = "fingerprint";
+ private static final int REQUEST_CODE_RESET = 0x00005001;
+
+ private static final String ARG_FINGERPRINTS = "fingerprint";
public static final String ARG_AID = "aid";
public static final String ARG_USER_ID = "user_ids";
@@ -62,6 +68,10 @@ public class CreateYubiKeyImportFragment
private ImportKeysListFragment mListFragment;
private TextView vSerNo;
private TextView vUserId;
+ private TextView mNextButton;
+ private RadioButton mRadioImport;
+ private RadioButton mRadioReset;
+ private View mResetWarning;
// for CryptoOperationFragment key import
private String mKeyserver;
@@ -69,10 +79,10 @@ public class CreateYubiKeyImportFragment
public static Fragment newInstance(byte[] scannedFingerprints, byte[] nfcAid, String userId) {
- CreateYubiKeyImportFragment frag = new CreateYubiKeyImportFragment();
+ CreateYubiKeyImportResetFragment frag = new CreateYubiKeyImportResetFragment();
Bundle args = new Bundle();
- args.putByteArray(ARG_FINGERPRINT, scannedFingerprints);
+ args.putByteArray(ARG_FINGERPRINTS, scannedFingerprints);
args.putByteArray(ARG_AID, nfcAid);
args.putString(ARG_USER_ID, userId);
frag.setArguments(args);
@@ -86,7 +96,7 @@ public class CreateYubiKeyImportFragment
Bundle args = savedInstanceState != null ? savedInstanceState : getArguments();
- mNfcFingerprints = args.getByteArray(ARG_FINGERPRINT);
+ mNfcFingerprints = args.getByteArray(ARG_FINGERPRINTS);
mNfcAid = args.getByteArray(ARG_AID);
mNfcUserId = args.getString(ARG_USER_ID);
@@ -98,49 +108,78 @@ public class CreateYubiKeyImportFragment
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.create_yubi_key_import_fragment, container, false);
+ View view = inflater.inflate(R.layout.create_yubi_key_import_reset_fragment, container, false);
vSerNo = (TextView) view.findViewById(R.id.yubikey_serno);
vUserId = (TextView) view.findViewById(R.id.yubikey_userid);
+ mNextButton = (TextView) view.findViewById(R.id.create_key_next_button);
+ mRadioImport = (RadioButton) view.findViewById(R.id.yubikey_decision_import);
+ mRadioReset = (RadioButton) view.findViewById(R.id.yubikey_decision_reset);
+ mResetWarning = view.findViewById(R.id.yubikey_import_reset_warning);
- {
- 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 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) {
+ mNextButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mRadioReset.isChecked()) {
+ resetCard();
+ } else {
importKey();
}
- });
- }
+ }
+ });
mListFragment = ImportKeysListFragment.newInstance(null, null,
"0x" + mNfcFingerprint, true, null);
- view.findViewById(R.id.button_search).setOnClickListener(new OnClickListener() {
+ mRadioImport.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
- public void onClick(View v) {
- refreshSearch();
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ if (isChecked) {
+ mNextButton.setText(R.string.btn_import);
+ mNextButton.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_key_plus_grey600_24dp, 0);
+ mNextButton.setVisibility(View.VISIBLE);
+ mResetWarning.setVisibility(View.GONE);
+
+ getFragmentManager().beginTransaction()
+ .replace(R.id.yubikey_import_fragment, mListFragment, "yubikey_import")
+ .commit();
+
+ getFragmentManager().executePendingTransactions();
+ refreshSearch();
+ }
+ }
+ });
+ mRadioReset.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ if (isChecked) {
+ mNextButton.setText(R.string.btn_reset);
+ mNextButton.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_close_grey_24dp, 0);
+ mNextButton.setVisibility(View.VISIBLE);
+ mResetWarning.setVisibility(View.VISIBLE);
+
+ getFragmentManager().beginTransaction()
+ .remove(mListFragment)
+ .commit();
+ }
}
});
setData();
- getFragmentManager().beginTransaction()
- .replace(R.id.yubikey_import_fragment, mListFragment, "yubikey_import")
- .commit();
return view;
}
@@ -149,7 +188,7 @@ public class CreateYubiKeyImportFragment
public void onSaveInstanceState(Bundle args) {
super.onSaveInstanceState(args);
- args.putByteArray(ARG_FINGERPRINT, mNfcFingerprints);
+ args.putByteArray(ARG_FINGERPRINTS, mNfcFingerprints);
args.putByteArray(ARG_AID, mNfcAid);
args.putString(ARG_USER_ID, mNfcUserId);
}
@@ -195,6 +234,25 @@ public class CreateYubiKeyImportFragment
}
+ public void resetCard() {
+ Intent intent = new Intent(getActivity(), NfcOperationActivity.class);
+ intent.putExtra(NfcOperationActivity.EXTRA_SERVICE_INTENT, (Parcelable[]) null);
+ RequiredInputParcel resetP = RequiredInputParcel.createNfcReset();
+ intent.putExtra(NfcOperationActivity.EXTRA_REQUIRED_INPUT, resetP);
+ intent.putExtra(NfcOperationActivity.EXTRA_CRYPTO_INPUT, new CryptoInputParcel());
+ startActivityForResult(intent, REQUEST_CODE_RESET);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == REQUEST_CODE_RESET && resultCode == Activity.RESULT_OK) {
+ mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT);
+ return;
+ }
+
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+
@Override
public void doNfcInBackground() throws IOException {
@@ -208,11 +266,10 @@ public class CreateYubiKeyImportFragment
}
@Override
- public void onNfcPostExecute() throws IOException {
+ public void onNfcPostExecute() {
setData();
- refreshSearch();
}
@Override
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinFragment.java
index a793b31f2..128383d6d 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinFragment.java
@@ -18,13 +18,15 @@
package org.sufficientlysecure.keychain.ui;
import android.app.Activity;
+import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.Fragment;
-import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
import android.widget.TextView;
import org.sufficientlysecure.keychain.R;
@@ -32,16 +34,34 @@ import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
import org.sufficientlysecure.keychain.util.Passphrase;
import java.security.SecureRandom;
+import java.util.Arrays;
+import java.util.HashSet;
public class CreateYubiKeyPinFragment extends Fragment {
// view
CreateKeyActivity mCreateKeyActivity;
- TextView mPin;
+ EditText mPin;
+ EditText mPinRepeat;
TextView mAdminPin;
View mBackButton;
View mNextButton;
+ private static HashSet<String> sPinBlacklist = new HashSet<>(Arrays.asList(
+ "000000",
+ "111111",
+ "222222",
+ "333333",
+ "444444",
+ "555555",
+ "666666",
+ "777777",
+ "888888",
+ "999999",
+ "123456",
+ "XXXXXX"
+ ));
+
/**
* Creates new instance of this fragment
*/
@@ -54,50 +74,71 @@ public class CreateYubiKeyPinFragment extends Fragment {
return frag;
}
+ /**
+ * Checks if text of given EditText is not empty. If it is empty an error is
+ * set and the EditText gets the focus.
+ *
+ * @return true if EditText is not empty
+ */
+ private static boolean isEditTextNotEmpty(Context context, EditText editText) {
+ boolean output = true;
+ if (editText.getText().length() == 0) {
+ editText.setError(context.getString(R.string.create_key_empty));
+ editText.requestFocus();
+ output = false;
+ } else {
+ editText.setError(null);
+ }
+
+ return output;
+ }
+
+ private static boolean areEditTextsEqual(EditText editText1, EditText editText2) {
+ Passphrase p1 = new Passphrase(editText1);
+ Passphrase p2 = new Passphrase(editText2);
+ return (p1.equals(p2));
+ }
+
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.create_yubi_key_pin_fragment, container, false);
- mPin = (TextView) view.findViewById(R.id.create_yubi_key_pin);
+ mPin = (EditText) view.findViewById(R.id.create_yubi_key_pin);
+ mPinRepeat = (EditText) view.findViewById(R.id.create_yubi_key_pin_repeat);
mAdminPin = (TextView) view.findViewById(R.id.create_yubi_key_admin_pin);
mBackButton = view.findViewById(R.id.create_key_back_button);
mNextButton = view.findViewById(R.id.create_key_next_button);
if (mCreateKeyActivity.mYubiKeyPin == null) {
- new AsyncTask<Void, Void, Pair<Passphrase, Passphrase>>() {
+ new AsyncTask<Void, Void, Passphrase>() {
@Override
- protected Pair<Passphrase, Passphrase> doInBackground(Void... unused) {
+ protected Passphrase doInBackground(Void... unused) {
SecureRandom secureRandom = new SecureRandom();
- // min = 6, we choose 6
- String pin = "" + secureRandom.nextInt(9)
+ // min = 8, we choose 8
+ String adminPin = "" + secureRandom.nextInt(9)
+ secureRandom.nextInt(9)
+ secureRandom.nextInt(9)
+ secureRandom.nextInt(9)
+ secureRandom.nextInt(9)
- + secureRandom.nextInt(9);
- // min = 8, we choose 10, but 6 are equals the PIN
- String adminPin = pin + secureRandom.nextInt(9)
+ secureRandom.nextInt(9)
+ secureRandom.nextInt(9)
+ secureRandom.nextInt(9);
- return new Pair<>(new Passphrase(pin), new Passphrase(adminPin));
+ return new Passphrase(adminPin);
}
@Override
- protected void onPostExecute(Pair<Passphrase, Passphrase> pair) {
- mCreateKeyActivity.mYubiKeyPin = pair.first;
- mCreateKeyActivity.mYubiKeyAdminPin = pair.second;
+ protected void onPostExecute(Passphrase adminPin) {
+ mCreateKeyActivity.mYubiKeyAdminPin = adminPin;
- mPin.setText(mCreateKeyActivity.mYubiKeyPin.toStringUnsafe());
mAdminPin.setText(mCreateKeyActivity.mYubiKeyAdminPin.toStringUnsafe());
}
}.execute();
} else {
- mPin.setText(mCreateKeyActivity.mYubiKeyPin.toStringUnsafe());
mAdminPin.setText(mCreateKeyActivity.mYubiKeyAdminPin.toStringUnsafe());
}
+ mPin.requestFocus();
mBackButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -111,7 +152,6 @@ public class CreateYubiKeyPinFragment extends Fragment {
}
});
-
return view;
}
@@ -121,14 +161,53 @@ public class CreateYubiKeyPinFragment extends Fragment {
mCreateKeyActivity = (CreateKeyActivity) getActivity();
}
+ private void back() {
+ hideKeyboard();
+ mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT);
+ }
private void nextClicked() {
- CreateYubiKeyPinRepeatFragment frag = CreateYubiKeyPinRepeatFragment.newInstance();
- mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT);
+ if (isEditTextNotEmpty(getActivity(), mPin)) {
+
+ if (!areEditTextsEqual(mPin, mPinRepeat)) {
+ mPinRepeat.setError(getString(R.string.create_key_passphrases_not_equal));
+ mPinRepeat.requestFocus();
+ return;
+ }
+
+ if (mPin.getText().toString().length() < 6) {
+ mPin.setError(getString(R.string.create_key_yubi_key_pin_too_short));
+ mPin.requestFocus();
+ return;
+ }
+
+ if (sPinBlacklist.contains(mPin.getText().toString())) {
+ mPin.setError(getString(R.string.create_key_yubi_key_pin_insecure));
+ mPin.requestFocus();
+ return;
+ }
+
+ mCreateKeyActivity.mYubiKeyPin = new Passphrase(mPin.getText().toString());
+
+ CreateKeyFinalFragment frag = CreateKeyFinalFragment.newInstance();
+ hideKeyboard();
+ mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT);
+ }
}
- private void back() {
- mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT);
+ private void hideKeyboard() {
+ if (getActivity() == null) {
+ return;
+ }
+ InputMethodManager inputManager = (InputMethodManager) getActivity()
+ .getSystemService(Context.INPUT_METHOD_SERVICE);
+
+ // check if no view has focus
+ View v = getActivity().getCurrentFocus();
+ if (v == null)
+ return;
+
+ inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0);
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinRepeatFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinRepeatFragment.java
deleted file mode 100644
index 2e752e609..000000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinRepeatFragment.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * 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;
-
-import android.app.Activity;
-import android.content.Context;
-import android.os.Bundle;
-import android.support.v4.app.Fragment;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.EditText;
-
-import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
-
-public class CreateYubiKeyPinRepeatFragment extends Fragment {
-
- // view
- CreateKeyActivity mCreateKeyActivity;
- EditText mPin;
- EditText mAdminPin;
- View mBackButton;
- View mNextButton;
-
- /**
- * Creates new instance of this fragment
- */
- public static CreateYubiKeyPinRepeatFragment newInstance() {
- CreateYubiKeyPinRepeatFragment frag = new CreateYubiKeyPinRepeatFragment();
-
- Bundle args = new Bundle();
- frag.setArguments(args);
-
- return frag;
- }
-
- /**
- * Checks if text of given EditText is not empty. If it is empty an error is
- * set and the EditText gets the focus.
- *
- * @param context
- * @param editText
- * @return true if EditText is not empty
- */
- private static boolean isEditTextNotEmpty(Context context, EditText editText) {
- boolean output = true;
- if (editText.getText().length() == 0) {
- editText.setError(context.getString(R.string.create_key_empty));
- editText.requestFocus();
- output = false;
- } else {
- editText.setError(null);
- }
-
- return output;
- }
-
- private static boolean checkPin(Context context, EditText editText1, String pin) {
- boolean output = editText1.getText().toString().equals(pin);
-
- if (!output) {
- editText1.setError(context.getString(R.string.create_key_yubi_key_pin_not_correct));
- editText1.requestFocus();
- } else {
- editText1.setError(null);
- }
-
- return output;
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.create_yubi_key_pin_repeat_fragment, container, false);
-
- mPin = (EditText) view.findViewById(R.id.create_yubi_key_pin_repeat);
- mAdminPin = (EditText) view.findViewById(R.id.create_yubi_key_admin_pin_repeat);
- mBackButton = view.findViewById(R.id.create_key_back_button);
- mNextButton = view.findViewById(R.id.create_key_next_button);
-
- mPin.requestFocus();
- mBackButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- back();
- }
- });
- mNextButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- nextClicked();
- }
- });
-
- return view;
- }
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- mCreateKeyActivity = (CreateKeyActivity) getActivity();
- }
-
- private void back() {
- hideKeyboard();
- mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT);
- }
-
- private void nextClicked() {
- if (isEditTextNotEmpty(getActivity(), mPin)
- && checkPin(getActivity(), mPin, mCreateKeyActivity.mYubiKeyPin.toStringUnsafe())
- && isEditTextNotEmpty(getActivity(), mAdminPin)
- && checkPin(getActivity(), mAdminPin, mCreateKeyActivity.mYubiKeyAdminPin.toStringUnsafe())) {
-
- CreateKeyFinalFragment frag = CreateKeyFinalFragment.newInstance();
- hideKeyboard();
- mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT);
- }
- }
-
- private void hideKeyboard() {
- if (getActivity() == null) {
- return;
- }
- InputMethodManager inputManager = (InputMethodManager) getActivity()
- .getSystemService(Context.INPUT_METHOD_SERVICE);
-
- // check if no view has focus
- View v = getActivity().getCurrentFocus();
- if (v == null)
- return;
-
- inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0);
- }
-
-}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java
index 4e9a6f17d..cf7a0b1d7 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java
@@ -37,7 +37,7 @@ import android.widget.Toast;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.intents.OpenKeychainIntents;
import org.sufficientlysecure.keychain.pgp.PgpHelper;
-import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
+import org.sufficientlysecure.keychain.provider.TemporaryFileProvider;
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
@@ -193,14 +193,14 @@ public class DecryptActivity extends BaseActivity {
@Nullable
public Uri readToTempFile(String text) throws IOException {
- Uri tempFile = TemporaryStorageProvider.createFile(this);
+ Uri tempFile = TemporaryFileProvider.createFile(this);
OutputStream outStream = getContentResolver().openOutputStream(tempFile);
if (outStream == null) {
return null;
}
// clean up ascii armored message, fixing newlines and stuff
- String cleanedText = PgpHelper.getPgpContent(text);
+ String cleanedText = PgpHelper.getPgpMessageContent(text);
if (cleanedText == null) {
return null;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java
index dcba595e9..8adaa0670 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java
@@ -22,13 +22,17 @@ import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
+import android.Manifest;
+import android.annotation.TargetApi;
import android.app.Activity;
import android.content.ClipDescription;
import android.content.Context;
import android.content.Intent;
import android.content.pm.LabeledIntent;
+import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.graphics.Point;
@@ -36,8 +40,12 @@ import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.Parcelable;
+import android.support.annotation.NonNull;
+import android.support.v4.content.ContextCompat;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
@@ -65,10 +73,14 @@ import org.openintents.openpgp.OpenPgpSignatureResult;
import org.sufficientlysecure.keychain.BuildConfig;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
+import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.operations.results.InputDataResult;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
+import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
import org.sufficientlysecure.keychain.service.InputDataParcel;
+import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment;
// this import NEEDS to be above the ViewModel AND SubViewHolder one, or it won't compile! (as of 16.09.15)
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.StatusHolder;
@@ -82,8 +94,25 @@ import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.util.FileHelper;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ParcelableHashMap;
+import org.sufficientlysecure.keychain.util.Preferences;
+/** Displays a list of decrypted inputs.
+ *
+ * This class has a complex control flow to manage its input URIs. Each URI
+ * which is in mInputUris is also in exactly one of mPendingInputUris,
+ * mCancelledInputUris, mCurrentInputUri, or a key in mInputDataResults.
+ *
+ * Processing of URIs happens using a looping approach:
+ * - There is always exactly one method running which works on mCurrentInputUri
+ * - Processing starts in cryptoOperation(), which pops a new mCurrentInputUri
+ * from the list of mPendingInputUris.
+ * - Once a mCurrentInputUri is finished processing, it should be set to null and
+ * control handed back to cryptoOperation()
+ * - Control flow can move through asynchronous calls, and resume in callbacks
+ * like onActivityResult() or onPermissionRequestResult().
+ *
+ */
public class DecryptListFragment
extends QueueingCryptoOperationFragment<InputDataParcel,InputDataResult>
implements OnMenuItemClickListener {
@@ -95,7 +124,7 @@ public class DecryptListFragment
public static final String ARG_CAN_DELETE = "can_delete";
private static final int REQUEST_CODE_OUTPUT = 0x00007007;
- public static final String ARG_CURRENT_URI = "current_uri";
+ private static final int REQUEST_PERMISSION_READ_EXTERNAL_STORAGE = 12;
private ArrayList<Uri> mInputUris;
private HashMap<Uri, InputDataResult> mInputDataResults;
@@ -111,7 +140,7 @@ public class DecryptListFragment
/**
* Creates new instance of this fragment
*/
- public static DecryptListFragment newInstance(ArrayList<Uri> uris, boolean canDelete) {
+ public static DecryptListFragment newInstance(@NonNull ArrayList<Uri> uris, boolean canDelete) {
DecryptListFragment frag = new DecryptListFragment();
Bundle args = new Bundle();
@@ -168,9 +197,12 @@ public class DecryptListFragment
outState.putParcelable(ARG_RESULTS, new ParcelableHashMap<>(results));
outState.putParcelable(ARG_OUTPUT_URIS, new ParcelableHashMap<>(mInputDataResults));
outState.putParcelableArrayList(ARG_CANCELLED_URIS, mCancelledInputUris);
- outState.putParcelable(ARG_CURRENT_URI, mCurrentInputUri);
outState.putBoolean(ARG_CAN_DELETE, mCanDelete);
+ // this does not save mCurrentInputUri - if anything is being
+ // processed at fragment recreation time, the operation in
+ // progress will be lost!
+
}
@Override
@@ -182,20 +214,21 @@ public class DecryptListFragment
ArrayList<Uri> inputUris = getArguments().getParcelableArrayList(ARG_INPUT_URIS);
ArrayList<Uri> cancelledUris = args.getParcelableArrayList(ARG_CANCELLED_URIS);
ParcelableHashMap<Uri,InputDataResult> results = args.getParcelable(ARG_RESULTS);
- Uri currentInputUri = args.getParcelable(ARG_CURRENT_URI);
mCanDelete = args.getBoolean(ARG_CAN_DELETE, false);
- displayInputUris(inputUris, currentInputUri, cancelledUris,
+ displayInputUris(inputUris, cancelledUris,
results != null ? results.getMap() : null
);
}
- private void displayInputUris(ArrayList<Uri> inputUris, Uri currentInputUri,
- ArrayList<Uri> cancelledUris, HashMap<Uri,InputDataResult> results) {
+ private void displayInputUris(
+ ArrayList<Uri> inputUris,
+ ArrayList<Uri> cancelledUris,
+ HashMap<Uri,InputDataResult> results) {
mInputUris = inputUris;
- mCurrentInputUri = currentInputUri;
+ mCurrentInputUri = null;
mInputDataResults = results != null ? results : new HashMap<Uri,InputDataResult>(inputUris.size());
mCancelledInputUris = cancelledUris != null ? cancelledUris : new ArrayList<Uri>();
@@ -204,30 +237,23 @@ public class DecryptListFragment
for (final Uri uri : inputUris) {
mAdapter.add(uri);
- if (uri.equals(mCurrentInputUri)) {
+ boolean uriIsCancelled = mCancelledInputUris.contains(uri);
+ if (uriIsCancelled) {
+ mAdapter.setCancelled(uri, true);
continue;
}
- if (mCancelledInputUris.contains(uri)) {
- mAdapter.setCancelled(uri, new OnClickListener() {
- @Override
- public void onClick(View v) {
- retryUri(uri);
- }
- });
+ boolean uriHasResult = results != null && results.containsKey(uri);
+ if (uriHasResult) {
+ processResult(uri);
continue;
}
- if (results != null && results.containsKey(uri)) {
- processResult(uri);
- } else {
- mPendingInputUris.add(uri);
- }
+ mPendingInputUris.add(uri);
}
- if (mCurrentInputUri == null) {
- cryptoOperation();
- }
+ // check if there are any pending input uris
+ cryptoOperation();
}
@Override
@@ -249,6 +275,7 @@ public class DecryptListFragment
}
}
+ @TargetApi(VERSION_CODES.KITKAT)
private void saveFileDialog(InputDataResult result, int index) {
Activity activity = getActivity();
@@ -257,17 +284,16 @@ public class DecryptListFragment
}
OpenPgpMetadata metadata = result.mMetadata.get(index);
- Uri saveUri = Uri.fromFile(activity.getExternalFilesDir(metadata.getMimeType()));
mCurrentSaveFileUri = result.getOutputUris().get(index);
String filename = metadata.getFilename();
- if (filename == null) {
+ if (TextUtils.isEmpty(filename)) {
String ext = MimeTypeMap.getSingleton().getExtensionFromMimeType(metadata.getMimeType());
filename = "decrypted" + (ext != null ? "."+ext : "");
}
- FileHelper.saveDocument(this, filename, saveUri, metadata.getMimeType(),
- R.string.title_decrypt_to_file, R.string.specify_file_to_decrypt_to, REQUEST_CODE_OUTPUT);
+ // requires >=kitkat
+ FileHelper.saveDocument(this, filename, metadata.getMimeType(), REQUEST_CODE_OUTPUT);
}
private void saveFile(Uri saveUri) {
@@ -320,6 +346,29 @@ public class DecryptListFragment
Uri uri = mCurrentInputUri;
mCurrentInputUri = null;
+ Activity activity = getActivity();
+
+ boolean isSingleInput = mInputDataResults.isEmpty() && mPendingInputUris.isEmpty();
+ if (isSingleInput) {
+
+ // there is always at least one mMetadata object, so we know this is >= 1 already
+ boolean isSingleMetadata = result.mMetadata.size() == 1;
+ OpenPgpMetadata metadata = result.mMetadata.get(0);
+ boolean isText = "text/plain".equals(metadata.getMimeType());
+ boolean isOverSized = metadata.getOriginalSize() > Constants.TEXT_LENGTH_LIMIT;
+
+ if (isSingleMetadata && isText && !isOverSized) {
+ Intent displayTextIntent = new Intent(activity, DisplayTextActivity.class)
+ .setDataAndType(result.mOutputUris.get(0), "text/plain")
+ .putExtra(DisplayTextActivity.EXTRA_RESULT, result.mDecryptVerifyResult)
+ .putExtra(DisplayTextActivity.EXTRA_METADATA, metadata);
+ activity.startActivity(displayTextIntent);
+ activity.finish();
+ return;
+ }
+
+ }
+
mInputDataResults.put(uri, result);
processResult(uri);
@@ -334,12 +383,7 @@ public class DecryptListFragment
mCurrentInputUri = null;
mCancelledInputUris.add(uri);
- mAdapter.setCancelled(uri, new OnClickListener() {
- @Override
- public void onClick(View v) {
- retryUri(uri);
- }
- });
+ mAdapter.setCancelled(uri, true);
cryptoOperation();
@@ -375,11 +419,20 @@ public class DecryptListFragment
if (ClipDescription.compareMimeTypes(type, "text/plain")) {
// noinspection deprecation, this should be called from Context, but not available in minSdk
icon = getResources().getDrawable(R.drawable.ic_chat_black_24dp);
+ } else if (ClipDescription.compareMimeTypes(type, "application/octet-stream")) {
+ // icons for this are just confusing
+ // noinspection deprecation, this should be called from Context, but not available in minSdk
+ icon = getResources().getDrawable(R.drawable.ic_doc_generic_am);
+ } else if (ClipDescription.compareMimeTypes(type, Constants.MIME_TYPE_KEYS)) {
+ // noinspection deprecation, this should be called from Context, but not available in minSdk
+ icon = getResources().getDrawable(R.drawable.ic_key_plus_grey600_24dp);
} else if (ClipDescription.compareMimeTypes(type, "image/*")) {
- int px = FormattingUtils.dpToPx(context, 48);
+ int px = FormattingUtils.dpToPx(context, 32);
Bitmap bitmap = FileHelper.getThumbnail(context, outputUri, new Point(px, px));
icon = new BitmapDrawable(context.getResources(), bitmap);
- } else {
+ }
+
+ if (icon == null) {
final Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(outputUri, type);
@@ -420,11 +473,12 @@ public class DecryptListFragment
// un-cancel this one
mCancelledInputUris.remove(uri);
+ mInputDataResults.remove(uri);
mPendingInputUris.add(uri);
- mAdapter.setCancelled(uri, null);
+ mAdapter.resetItemData(uri);
+ // check if there are any pending input uris
cryptoOperation();
-
}
public void displayBottomSheet(final InputDataResult result, final int index) {
@@ -445,6 +499,7 @@ public class DecryptListFragment
displayWithViewIntent(result, index, true, true);
break;
case R.id.decrypt_save:
+ // only inside the menu xml for Android >= 4.4
saveFileDialog(result, index);
break;
}
@@ -525,6 +580,11 @@ public class DecryptListFragment
} else {
intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(outputUri, metadata.getMimeType());
+
+ if (!forceChooser && Constants.MIME_TYPE_KEYS.equals(metadata.getMimeType())) {
+ // bind Intent to this OpenKeychain, don't allow other apps to intercept here!
+ intent.setPackage(getActivity().getPackageName());
+ }
}
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
@@ -539,6 +599,11 @@ public class DecryptListFragment
@Override
public InputDataParcel createOperationInput() {
+ Activity activity = getActivity();
+ if (activity == null) {
+ return null;
+ }
+
if (mCurrentInputUri == null) {
if (mPendingInputUris.isEmpty()) {
// nothing left to do
@@ -548,7 +613,11 @@ public class DecryptListFragment
mCurrentInputUri = mPendingInputUris.remove(0);
}
- Log.d(Constants.TAG, "mInputUri=" + mCurrentInputUri);
+ Log.d(Constants.TAG, "mCurrentInputUri=" + mCurrentInputUri);
+
+ if ( ! checkAndRequestReadPermission(activity, mCurrentInputUri)) {
+ return null;
+ }
PgpDecryptVerifyInputParcel decryptInput = new PgpDecryptVerifyInputParcel()
.setAllowSymmetricDecryption(true);
@@ -556,6 +625,94 @@ public class DecryptListFragment
}
+ /**
+ * Request READ_EXTERNAL_STORAGE permission on Android >= 6.0 to read content from "file" Uris.
+ *
+ * This method returns true on Android < 6, or if permission is already granted. It
+ * requests the permission and returns false otherwise, taking over responsibility
+ * for mCurrentInputUri.
+ *
+ * see https://commonsware.com/blog/2015/10/07/runtime-permissions-files-action-send.html
+ */
+ private boolean checkAndRequestReadPermission(Activity activity, final Uri uri) {
+ if ( ! "file".equals(uri.getScheme())) {
+ return true;
+ }
+
+ if (Build.VERSION.SDK_INT < VERSION_CODES.M) {
+ return true;
+ }
+
+ // Additional check due to https://commonsware.com/blog/2015/11/09/you-cannot-hold-nonexistent-permissions.html
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
+ return true;
+ }
+
+ if (ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE)
+ == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
+
+ requestPermissions(
+ new String[] { Manifest.permission.READ_EXTERNAL_STORAGE },
+ REQUEST_PERMISSION_READ_EXTERNAL_STORAGE);
+
+ return false;
+
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode,
+ @NonNull String[] permissions,
+ @NonNull int[] grantResults) {
+
+ if (requestCode != REQUEST_PERMISSION_READ_EXTERNAL_STORAGE) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ return;
+ }
+
+ boolean permissionWasGranted = grantResults.length > 0
+ && grantResults[0] == PackageManager.PERMISSION_GRANTED;
+
+ if (permissionWasGranted) {
+
+ // permission granted -> retry all cancelled file uris
+ Iterator<Uri> it = mCancelledInputUris.iterator();
+ while (it.hasNext()) {
+ Uri uri = it.next();
+ if ( ! "file".equals(uri.getScheme())) {
+ continue;
+ }
+ it.remove();
+ mPendingInputUris.add(uri);
+ mAdapter.setCancelled(uri, false);
+ }
+
+ } else {
+
+ // permission denied -> cancel current, and all pending file uris
+ mCancelledInputUris.add(mCurrentInputUri);
+ mAdapter.setCancelled(mCurrentInputUri, true);
+
+ mCurrentInputUri = null;
+ Iterator<Uri> it = mPendingInputUris.iterator();
+ while (it.hasNext()) {
+ Uri uri = it.next();
+ if ( ! "file".equals(uri.getScheme())) {
+ continue;
+ }
+ it.remove();
+ mCancelledInputUris.add(uri);
+ mAdapter.setCancelled(uri, true);
+ }
+
+ }
+
+ // hand control flow back
+ cryptoOperation();
+
+ }
+
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
if (mAdapter.mMenuClickedModel == null || !mAdapter.mMenuClickedModel.hasResult()) {
@@ -586,38 +743,83 @@ public class DecryptListFragment
return false;
}
+ private void lookupUnknownKey(final Uri inputUri, long unknownKeyId) {
+
+ final ArrayList<ParcelableKeyRing> keyList;
+ final String keyserver;
+
+ // search config
+ {
+ Preferences prefs = Preferences.getPreferences(getActivity());
+ Preferences.CloudSearchPrefs cloudPrefs =
+ new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver());
+ keyserver = cloudPrefs.keyserver;
+ }
+
+ {
+ ParcelableKeyRing keyEntry = new ParcelableKeyRing(null,
+ KeyFormattingUtils.convertKeyIdToHex(unknownKeyId), null);
+ ArrayList<ParcelableKeyRing> selectedEntries = new ArrayList<>();
+ selectedEntries.add(keyEntry);
+
+ keyList = selectedEntries;
+ }
+
+ CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult> callback
+ = new CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult>() {
+
+ @Override
+ public ImportKeyringParcel createOperationInput() {
+ return new ImportKeyringParcel(keyList, keyserver);
+ }
+
+ @Override
+ public void onCryptoOperationSuccess(ImportKeyResult result) {
+ retryUri(inputUri);
+ }
+
+ @Override
+ public void onCryptoOperationCancelled() {
+ mAdapter.setProcessingKeyLookup(inputUri, false);
+ }
+
+ @Override
+ public void onCryptoOperationError(ImportKeyResult result) {
+ result.createNotify(getActivity()).show();
+ mAdapter.setProcessingKeyLookup(inputUri, false);
+ }
+
+ @Override
+ public boolean onCryptoSetProgress(String msg, int progress, int max) {
+ return false;
+ }
+ };
+
+ mAdapter.setProcessingKeyLookup(inputUri, true);
+
+ CryptoOperationHelper importOpHelper = new CryptoOperationHelper<>(2, this, callback, null);
+ importOpHelper.cryptoOperation();
+
+ }
+
+
private void deleteFile(Activity activity, Uri uri) {
// we can only ever delete a file once, if we got this far either it's gone or it will never work
mCanDelete = false;
- if ("file".equals(uri.getScheme())) {
- File file = new File(uri.getPath());
- if (file.delete()) {
+ try {
+ int deleted = FileHelper.deleteFileSecurely(activity, uri);
+ if (deleted > 0) {
Notify.create(activity, R.string.file_delete_ok, Style.OK).show();
} else {
Notify.create(activity, R.string.file_delete_none, Style.WARN).show();
}
- return;
- }
-
- if ("content".equals(uri.getScheme())) {
- try {
- int deleted = activity.getContentResolver().delete(uri, null, null);
- if (deleted > 0) {
- Notify.create(activity, R.string.file_delete_ok, Style.OK).show();
- } else {
- Notify.create(activity, R.string.file_delete_none, Style.WARN).show();
- }
- } catch (Exception e) {
- Log.e(Constants.TAG, "exception deleting file", e);
- Notify.create(activity, R.string.file_delete_exception, Style.ERROR).show();
- }
- return;
+ } catch (Exception e) {
+ Log.e(Constants.TAG, "exception deleting file", e);
+ Notify.create(activity, R.string.file_delete_exception, Style.ERROR).show();
}
- Notify.create(activity, R.string.file_delete_exception, Style.ERROR).show();
-
}
public class DecryptFilesAdapter extends RecyclerView.Adapter<ViewHolder> {
@@ -631,6 +833,7 @@ public class DecryptListFragment
int mProgress, mMax;
String mProgressMsg;
OnClickListener mCancelled;
+ boolean mProcessingKeyLookup;
ViewModel(Uri uri) {
mInputUri = uri;
@@ -639,7 +842,7 @@ public class DecryptListFragment
mCancelled = null;
}
- void addResult(InputDataResult result) {
+ void setResult(InputDataResult result) {
mResult = result;
}
@@ -659,6 +862,10 @@ public class DecryptListFragment
mMax = max;
}
+ void setProcessingKeyLookup(boolean processingKeyLookup) {
+ mProcessingKeyLookup = processingKeyLookup;
+ }
+
// Depends on inputUri only
@Override
public boolean equals(Object o) {
@@ -669,8 +876,10 @@ public class DecryptListFragment
return false;
}
ViewModel viewModel = (ViewModel) o;
- return !(mInputUri != null ? !mInputUri.equals(viewModel.mInputUri)
- : viewModel.mInputUri != null);
+ if (mInputUri == null) {
+ return viewModel.mInputUri == null;
+ }
+ return mInputUri.equals(viewModel.mInputUri);
}
// Depends on inputUri only
@@ -725,17 +934,13 @@ public class DecryptListFragment
}
private void bindItemCancelled(ViewHolder holder, ViewModel model) {
- if (holder.vAnimator.getDisplayedChild() != 3) {
- holder.vAnimator.setDisplayedChild(3);
- }
+ holder.vAnimator.setDisplayedChild(3);
holder.vCancelledRetry.setOnClickListener(model.mCancelled);
}
private void bindItemProgress(ViewHolder holder, ViewModel model) {
- if (holder.vAnimator.getDisplayedChild() != 0) {
- holder.vAnimator.setDisplayedChild(0);
- }
+ holder.vAnimator.setDisplayedChild(0);
holder.vProgress.setProgress(model.mProgress);
holder.vProgress.setMax(model.mMax);
@@ -745,11 +950,10 @@ public class DecryptListFragment
}
private void bindItemSuccess(ViewHolder holder, final ViewModel model) {
- if (holder.vAnimator.getDisplayedChild() != 1) {
- holder.vAnimator.setDisplayedChild(1);
- }
+ holder.vAnimator.setDisplayedChild(1);
- KeyFormattingUtils.setStatus(getResources(), holder, model.mResult.mDecryptVerifyResult);
+ KeyFormattingUtils.setStatus(getResources(), holder,
+ model.mResult.mDecryptVerifyResult, model.mProcessingKeyLookup);
int numFiles = model.mResult.getOutputUris().size();
holder.resizeFileList(numFiles, LayoutInflater.from(getActivity()));
@@ -762,11 +966,14 @@ public class DecryptListFragment
String filename;
if (metadata == null) {
filename = getString(R.string.filename_unknown);
- } else if (TextUtils.isEmpty(metadata.getFilename())) {
- filename = getString("text/plain".equals(metadata.getMimeType())
- ? R.string.filename_unknown_text : R.string.filename_unknown);
- } else {
+ } else if ( ! TextUtils.isEmpty(metadata.getFilename())) {
filename = metadata.getFilename();
+ } else if (ClipDescription.compareMimeTypes(metadata.getMimeType(), Constants.MIME_TYPE_KEYS)) {
+ filename = getString(R.string.filename_keys);
+ } else if (ClipDescription.compareMimeTypes(metadata.getMimeType(), "text/plain")) {
+ filename = getString(R.string.filename_unknown_text);
+ } else {
+ filename = getString(R.string.filename_unknown);
}
fileHolder.vFilename.setText(filename);
@@ -824,6 +1031,13 @@ public class DecryptListFragment
activity.startActivity(intent);
}
});
+ } else {
+ holder.vSignatureLayout.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ lookupUnknownKey(model.mInputUri, keyId);
+ }
+ });
}
}
@@ -852,9 +1066,7 @@ public class DecryptListFragment
}
private void bindItemFailure(ViewHolder holder, final ViewModel model) {
- if (holder.vAnimator.getDisplayedChild() != 2) {
- holder.vAnimator.setDisplayedChild(2);
- }
+ holder.vAnimator.setDisplayedChild(2);
holder.vErrorMsg.setText(model.mResult.getLog().getLast().mType.getMsgId());
@@ -903,21 +1115,44 @@ public class DecryptListFragment
notifyItemChanged(pos);
}
- public void setCancelled(Uri uri, OnClickListener retryListener) {
+ public void setCancelled(final Uri uri, boolean isCancelled) {
ViewModel newModel = new ViewModel(uri);
int pos = mDataset.indexOf(newModel);
- mDataset.get(pos).setCancelled(retryListener);
+ if (isCancelled) {
+ mDataset.get(pos).setCancelled(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ retryUri(uri);
+ }
+ });
+ } else {
+ mDataset.get(pos).setCancelled(null);
+ }
notifyItemChanged(pos);
}
- public void addResult(Uri uri, InputDataResult result) {
+ public void setProcessingKeyLookup(Uri uri, boolean processingKeyLookup) {
+ ViewModel newModel = new ViewModel(uri);
+ int pos = mDataset.indexOf(newModel);
+ mDataset.get(pos).setProcessingKeyLookup(processingKeyLookup);
+ notifyItemChanged(pos);
+ }
+ public void addResult(Uri uri, InputDataResult result) {
ViewModel model = new ViewModel(uri);
int pos = mDataset.indexOf(model);
model = mDataset.get(pos);
+ model.setResult(result);
+ notifyItemChanged(pos);
+ }
- model.addResult(result);
-
+ public void resetItemData(Uri uri) {
+ ViewModel model = new ViewModel(uri);
+ int pos = mDataset.indexOf(model);
+ model = mDataset.get(pos);
+ model.setResult(null);
+ model.setCancelled(null);
+ model.setProcessingKeyLookup(false);
notifyItemChanged(pos);
}
@@ -941,7 +1176,7 @@ public class DecryptListFragment
public View vSignatureLayout;
public TextView vSignatureName;
public TextView vSignatureMail;
- public TextView vSignatureAction;
+ public ViewAnimator vSignatureAction;
public View vContextMenu;
public TextView vErrorMsg;
@@ -984,7 +1219,7 @@ public class DecryptListFragment
vSignatureLayout = itemView.findViewById(R.id.result_signature_layout);
vSignatureName = (TextView) itemView.findViewById(R.id.result_signature_name);
vSignatureMail= (TextView) itemView.findViewById(R.id.result_signature_email);
- vSignatureAction = (TextView) itemView.findViewById(R.id.result_signature_action);
+ vSignatureAction = (ViewAnimator) itemView.findViewById(R.id.result_signature_action);
vFileList = (LinearLayout) itemView.findViewById(R.id.file_list);
for (int i = 0; i < vFileList.getChildCount(); i++) {
@@ -1048,7 +1283,7 @@ public class DecryptListFragment
}
@Override
- public TextView getSignatureAction() {
+ public ViewAnimator getSignatureAction() {
return vSignatureAction;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java
index 4bcca09f1..2fa1aa1c2 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java
@@ -28,9 +28,12 @@ import android.support.v4.app.Fragment;
import android.widget.Toast;
import org.openintents.openpgp.OpenPgpMetadata;
+import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
+import org.sufficientlysecure.keychain.ui.util.Notify;
+import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.util.FileHelper;
public class DisplayTextActivity extends BaseActivity {
@@ -73,6 +76,11 @@ public class DisplayTextActivity extends BaseActivity {
}
if (plaintext != null) {
+ if (plaintext.length() > Constants.TEXT_LENGTH_LIMIT) {
+ plaintext = plaintext.substring(0, Constants.TEXT_LENGTH_LIMIT);
+ Notify.create(this, R.string.snack_text_too_long, Style.WARN).show();
+ }
+
loadFragment(plaintext, result);
} else {
Toast.makeText(this, R.string.error_invalid_data, Toast.LENGTH_LONG).show();
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java
index dc06e9115..1060714f0 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java
@@ -36,7 +36,6 @@ import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
-import org.sufficientlysecure.keychain.util.ShareHelper;
public class DisplayTextFragment extends DecryptFragment {
@@ -60,22 +59,6 @@ public class DisplayTextFragment extends DecryptFragment {
return frag;
}
- /**
- * Create Intent Chooser but exclude decrypt activites
- */
- private Intent sendWithChooserExcludingDecrypt(String text) {
- Intent prototype = createSendIntent(text);
- String title = getString(R.string.title_share_message);
-
- // we don't want to decrypt the decrypted, no inception ;)
- String[] blacklist = new String[]{
- Constants.PACKAGE_NAME + ".ui.DecryptActivity",
- "org.thialfihar.android.apg.ui.DecryptActivity"
- };
-
- return new ShareHelper(getActivity()).createChooserExcluding(prototype, title, blacklist);
- }
-
private Intent createSendIntent(String text) {
Intent sendIntent = new Intent(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, text);
@@ -146,7 +129,8 @@ public class DisplayTextFragment extends DecryptFragment {
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.decrypt_share: {
- startActivity(sendWithChooserExcludingDecrypt(mText.getText().toString()));
+ startActivity(Intent.createChooser(createSendIntent(mText.getText().toString()),
+ getString(R.string.title_share_message)));
break;
}
case R.id.decrypt_copy: {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptFragment.java
index 84660ca7a..89ea6165b 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptFragment.java
@@ -25,7 +25,6 @@ import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
-import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
@@ -42,7 +41,7 @@ import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.ui.util.SubtleAttentionSeeker;
import org.sufficientlysecure.keychain.util.FileHelper;
-public class EncryptDecryptOverviewFragment extends Fragment {
+public class EncryptDecryptFragment extends Fragment {
View mClipboardIcon;
@@ -56,7 +55,7 @@ public class EncryptDecryptOverviewFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.encrypt_decrypt_overview_fragment, container, false);
+ View view = inflater.inflate(R.layout.encrypt_decrypt_fragment, container, false);
View mEncryptFile = view.findViewById(R.id.encrypt_files);
View mEncryptText = view.findViewById(R.id.encrypt_text);
@@ -83,7 +82,7 @@ public class EncryptDecryptOverviewFragment extends Fragment {
mDecryptFile.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- FileHelper.openDocument(EncryptDecryptOverviewFragment.this, null, "*/*", false, REQUEST_CODE_INPUT);
+ FileHelper.openDocument(EncryptDecryptFragment.this, null, "*/*", false, REQUEST_CODE_INPUT);
}
});
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 8572a5712..ff1b9d478 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java
@@ -25,6 +25,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import android.annotation.TargetApi;
import android.app.Activity;
import android.content.ClipData;
import android.content.ClipboardManager;
@@ -34,6 +35,7 @@ import android.graphics.Bitmap;
import android.graphics.Point;
import android.net.Uri;
import android.os.Build;
+import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v7.widget.DefaultItemAnimator;
@@ -55,7 +57,7 @@ import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants;
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
-import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
+import org.sufficientlysecure.keychain.provider.TemporaryFileProvider;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.adapter.SpacesItemDecoration;
import org.sufficientlysecure.keychain.ui.base.CachingCryptoOperationFragment;
@@ -68,7 +70,6 @@ import org.sufficientlysecure.keychain.util.FileHelper;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.Preferences;
-import org.sufficientlysecure.keychain.util.ShareHelper;
public class EncryptFilesFragment
extends CachingCryptoOperationFragment<SignEncryptParcel, SignEncryptResult> {
@@ -216,6 +217,7 @@ public class EncryptFilesFragment
mSelectedFiles.requestFocus();
}
+ @TargetApi(VERSION_CODES.KITKAT)
private void showOutputFileDialog() {
if (mFilesAdapter.getModelCount() != 1) {
throw new IllegalStateException();
@@ -224,9 +226,7 @@ public class EncryptFilesFragment
String targetName =
(mEncryptFilenames ? "1" : FileHelper.getFilename(getActivity(), model.inputUri))
+ (mUseArmor ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN);
- Uri inputUri = model.inputUri;
- FileHelper.saveDocument(this, targetName, inputUri,
- R.string.title_encrypt_to_file, R.string.specify_file_to_encrypt_to, REQUEST_CODE_OUTPUT);
+ FileHelper.saveDocument(this, targetName, REQUEST_CODE_OUTPUT);
}
public void addFile(Intent data) {
@@ -308,6 +308,17 @@ public class EncryptFilesFragment
return true;
}
+ @Override
+ public void onPrepareOptionsMenu(Menu menu) {
+ super.onPrepareOptionsMenu(menu);
+
+ // Show save only on Android >= 4.4 (Document Provider)
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
+ MenuItem save = menu.findItem(R.id.encrypt_save);
+ save.setVisible(false);
+ }
+ }
+
public void toggleUseArmor(MenuItem item, final boolean useArmor) {
mUseArmor = useArmor;
@@ -393,7 +404,7 @@ public class EncryptFilesFragment
public void onDeleted() {
if (mAfterEncryptAction == AfterEncryptAction.SHARE) {
// Share encrypted message/file
- startActivity(sendWithChooserExcludingEncrypt());
+ startActivity(Intent.createChooser(createSendIntent(), getString(R.string.title_share_file)));
} else {
Activity activity = getActivity();
if (activity == null) {
@@ -413,7 +424,7 @@ public class EncryptFilesFragment
case SHARE:
// Share encrypted message/file
- startActivity(sendWithChooserExcludingEncrypt());
+ startActivity(Intent.createChooser(createSendIntent(), getString(R.string.title_share_file)));
break;
case COPY:
@@ -455,7 +466,7 @@ public class EncryptFilesFragment
String 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));
+ mOutputUris.add(TemporaryFileProvider.createFile(getActivity(), targetName));
filenameCounter++;
}
return false;
@@ -478,7 +489,7 @@ public class EncryptFilesFragment
String targetName = (mEncryptFilenames
? String.valueOf(1) : FileHelper.getFilename(getActivity(),
mFilesAdapter.getModelItem(0).inputUri)) + Constants.FILE_EXTENSION_ASC;
- mOutputUris.add(TemporaryStorageProvider.createFile(getActivity(), targetName, "text/plain"));
+ mOutputUris.add(TemporaryFileProvider.createFile(getActivity(), targetName, "text/plain"));
return false;
}
@@ -587,22 +598,6 @@ public class EncryptFilesFragment
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.EncryptFilesActivity",
- "org.thialfihar.android.apg.ui.EncryptActivity"
- };
-
- return new ShareHelper(getActivity()).createChooserExcluding(prototype, title, blacklist);
- }
-
private Intent createSendIntent() {
Intent sendIntent;
// file
@@ -613,7 +608,7 @@ public class EncryptFilesFragment
sendIntent = new Intent(Intent.ACTION_SEND_MULTIPLE);
sendIntent.putExtra(Intent.EXTRA_STREAM, mOutputUris);
}
- sendIntent.setType(Constants.ENCRYPTED_FILES_MIME);
+ sendIntent.setType(Constants.MIME_TYPE_ENCRYPTED_ALTERNATE);
EncryptActivity modeInterface = (EncryptActivity) getActivity();
EncryptModeFragment modeFragment = modeInterface.getModeFragment();
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 a849cdf12..50dbc9a8b 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java
@@ -18,14 +18,23 @@
package org.sufficientlysecure.keychain.ui;
+
+import java.io.IOException;
+
import android.app.Activity;
import android.content.Intent;
+import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.FragmentTransaction;
-import android.view.View;
+import android.widget.Toast;
+import org.apache.james.mime4j.util.MimeUtil;
+import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.intents.OpenKeychainIntents;
+import org.sufficientlysecure.keychain.ui.util.Notify;
+import org.sufficientlysecure.keychain.ui.util.Notify.Style;
+import org.sufficientlysecure.keychain.util.FileHelper;
import org.sufficientlysecure.keychain.util.Log;
public class EncryptTextActivity extends EncryptActivity {
@@ -51,23 +60,71 @@ public class EncryptTextActivity extends EncryptActivity {
extras = new Bundle();
}
+ String textData = extras.getString(EXTRA_TEXT);
+ boolean returnProcessText = false;
+
// When sending to OpenKeychain Encrypt via share menu
if (Intent.ACTION_SEND.equals(action) && type != null) {
Log.logDebugBundle(extras, "extras");
// When sending to OpenKeychain Encrypt via share menu
- if ("text/plain".equals(type)) {
- String sharedText = extras.getString(Intent.EXTRA_TEXT);
- if (sharedText != null) {
- // handle like normal text encryption, override action and extras to later
- // executeServiceMethod ACTION_ENCRYPT_TEXT in main actions
- extras.putString(EXTRA_TEXT, sharedText);
+ if ( ! MimeUtil.isSameMimeType("text/plain", type)) {
+ Toast.makeText(this, R.string.toast_wrong_mimetype, Toast.LENGTH_LONG).show();
+ finish();
+ return;
+ }
+
+ String sharedText;
+ if (extras.containsKey(Intent.EXTRA_TEXT)) {
+ sharedText = extras.getString(Intent.EXTRA_TEXT);
+ } else if (extras.containsKey(Intent.EXTRA_STREAM)) {
+ try {
+ sharedText = FileHelper.readTextFromUri(this, extras.<Uri>getParcelable(Intent.EXTRA_STREAM), null);
+ } catch (IOException e) {
+ Toast.makeText(this, R.string.error_preparing_data, Toast.LENGTH_LONG).show();
+ finish();
+ return;
}
+ } else {
+ Toast.makeText(this, R.string.toast_no_text, Toast.LENGTH_LONG).show();
+ finish();
+ return;
+ }
+ if (sharedText != null) {
+ if (sharedText.length() > Constants.TEXT_LENGTH_LIMIT) {
+ sharedText = sharedText.substring(0, Constants.TEXT_LENGTH_LIMIT);
+ Notify.create(this, R.string.snack_shared_text_too_long, Style.WARN).show();
+ }
+ // handle like normal text encryption, override action and extras to later
+ // executeServiceMethod ACTION_ENCRYPT_TEXT in main actions
+ textData = sharedText;
+ }
+
+ }
+
+ // Android 6, PROCESS_TEXT Intent
+ if (Intent.ACTION_PROCESS_TEXT.equals(action) && type != null) {
+
+ String sharedText = null;
+ if (extras.containsKey(Intent.EXTRA_PROCESS_TEXT)) {
+ sharedText = extras.getString(Intent.EXTRA_PROCESS_TEXT);
+ returnProcessText = true;
+ } else if (extras.containsKey(Intent.EXTRA_PROCESS_TEXT_READONLY)) {
+ sharedText = extras.getString(Intent.EXTRA_PROCESS_TEXT_READONLY);
+ }
+
+ if (sharedText != null) {
+ if (sharedText.length() > Constants.TEXT_LENGTH_LIMIT) {
+ sharedText = sharedText.substring(0, Constants.TEXT_LENGTH_LIMIT);
+ Notify.create(this, R.string.snack_shared_text_too_long, Style.WARN).show();
+ }
+ // handle like normal text encryption, override action and extras to later
+ // executeServiceMethod ACTION_ENCRYPT_TEXT in main actions
+ textData = sharedText;
}
}
- String textData = extras.getString(EXTRA_TEXT);
if (textData == null) {
textData = "";
}
@@ -75,7 +132,7 @@ public class EncryptTextActivity extends EncryptActivity {
if (savedInstanceState == null) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
- EncryptTextFragment encryptFragment = EncryptTextFragment.newInstance(textData);
+ EncryptTextFragment encryptFragment = EncryptTextFragment.newInstance(textData, returnProcessText);
transaction.replace(R.id.encrypt_text_container, encryptFragment);
transaction.commit();
}
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 ab676285e..10d88253d 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java
@@ -46,7 +46,6 @@ import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.util.Passphrase;
import org.sufficientlysecure.keychain.util.Preferences;
-import org.sufficientlysecure.keychain.util.ShareHelper;
import java.util.Date;
import java.util.HashSet;
@@ -57,8 +56,10 @@ public class EncryptTextFragment
public static final String ARG_TEXT = "text";
public static final String ARG_USE_COMPRESSION = "use_compression";
+ public static final String ARG_RETURN_PROCESS_TEXT = "return_process_text";
private boolean mShareAfterEncrypt;
+ private boolean mReturnProcessTextAfterEncrypt;
private boolean mUseCompression;
private boolean mHiddenRecipients = false;
@@ -67,11 +68,12 @@ public class EncryptTextFragment
/**
* Creates new instance of this fragment
*/
- public static EncryptTextFragment newInstance(String text) {
+ public static EncryptTextFragment newInstance(String text, boolean returnProcessTextAfterEncrypt) {
EncryptTextFragment frag = new EncryptTextFragment();
Bundle args = new Bundle();
args.putString(ARG_TEXT, text);
+ args.putBoolean(ARG_RETURN_PROCESS_TEXT, returnProcessTextAfterEncrypt);
frag.setArguments(args);
return frag;
@@ -129,6 +131,7 @@ public class EncryptTextFragment
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
mMessage = getArguments().getString(ARG_TEXT);
+ mReturnProcessTextAfterEncrypt = getArguments().getBoolean(ARG_RETURN_PROCESS_TEXT, false);
}
Preferences prefs = Preferences.getPreferences(getActivity());
@@ -152,6 +155,12 @@ public class EncryptTextFragment
inflater.inflate(R.menu.encrypt_text_fragment, menu);
menu.findItem(R.id.check_enable_compression).setChecked(mUseCompression);
+
+ if (mReturnProcessTextAfterEncrypt) {
+ menu.findItem(R.id.encrypt_paste).setVisible(true);
+ menu.findItem(R.id.encrypt_copy).setVisible(false);
+ menu.findItem(R.id.encrypt_share).setVisible(false);
+ }
}
@Override
@@ -178,6 +187,11 @@ public class EncryptTextFragment
cryptoOperation(new CryptoInputParcel(new Date()));
break;
}
+ case R.id.encrypt_paste: {
+ hideKeyboard();
+ cryptoOperation(new CryptoInputParcel(new Date()));
+ break;
+ }
default: {
return super.onOptionsItemSelected(item);
}
@@ -289,26 +303,10 @@ public class EncryptTextFragment
result.createNotify(activity).show();
}
- /**
- * 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.setType(Constants.MIME_TYPE_TEXT);
sendIntent.putExtra(Intent.EXTRA_TEXT, new String(resultBytes));
EncryptActivity modeInterface = (EncryptActivity) getActivity();
@@ -343,7 +341,13 @@ public class EncryptTextFragment
if (mShareAfterEncrypt) {
// Share encrypted message/file
- startActivity(sendWithChooserExcludingEncrypt(result.getResultBytes()));
+ startActivity(Intent.createChooser(createSendIntent(result.getResultBytes()),
+ getString(R.string.title_share_message)));
+ } else if (mReturnProcessTextAfterEncrypt) {
+ Intent resultIntent = new Intent();
+ resultIntent.putExtra(Intent.EXTRA_PROCESS_TEXT, new String(result.getResultBytes()));
+ getActivity().setResult(Activity.RESULT_OK, resultIntent);
+ getActivity().finish();
} else {
// Copy to clipboard
copyToClipboard(result);
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 4ef6c40dc..7f7532ddf 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java
@@ -21,8 +21,8 @@ import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Message;
import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
@@ -33,10 +33,8 @@ import org.sufficientlysecure.keychain.intents.OpenKeychainIntents;
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.service.ImportKeyringParcel;
import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
-import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Notify;
@@ -78,10 +76,8 @@ public class ImportKeysActivity extends BaseNfcActivity
public static final String EXTRA_PENDING_INTENT_DATA = "data";
private Intent mPendingIntentData;
- // view
- private ImportKeysListFragment mListFragment;
- private Fragment mTopFragment;
- private View mImportButton;
+ public static final String TAG_FRAG_LIST = "frag_list";
+ public static final String TAG_FRAG_TOP = "frag_top";
// for CryptoOperationHelper.Callback
private String mKeyserver;
@@ -94,15 +90,22 @@ public class ImportKeysActivity extends BaseNfcActivity
super.onCreate(savedInstanceState);
setFullScreenDialogClose(Activity.RESULT_CANCELED, true);
- mImportButton = findViewById(R.id.import_import);
- mImportButton.setOnClickListener(new OnClickListener() {
+ findViewById(R.id.import_import).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
- importKeys();
+ importSelectedKeys();
}
});
- handleActions(savedInstanceState, getIntent());
+ // only used for OpenPgpService
+ if (getIntent().hasExtra(EXTRA_PENDING_INTENT_DATA)) {
+ mPendingIntentData = getIntent().getParcelableExtra(EXTRA_PENDING_INTENT_DATA);
+ }
+
+ // if we aren't being restored, initialize fragments
+ if (savedInstanceState == null) {
+ handleActions(getIntent());
+ }
}
@Override
@@ -110,7 +113,7 @@ public class ImportKeysActivity extends BaseNfcActivity
setContentView(R.layout.import_keys_activity);
}
- protected void handleActions(Bundle savedInstanceState, Intent intent) {
+ protected void handleActions(Intent intent) {
String action = intent.getAction();
Bundle extras = intent.getExtras();
Uri dataUri = intent.getData();
@@ -120,14 +123,8 @@ public class ImportKeysActivity extends BaseNfcActivity
extras = new Bundle();
}
- if (action == null) {
- startCloudFragment(savedInstanceState, null, false, null);
- startListFragment(savedInstanceState, null, null, null, null);
- return;
- }
-
if (Intent.ACTION_VIEW.equals(action)) {
- if (scheme.equals("http") || scheme.equals("https")) {
+ if ("http".equals(scheme) || "https".equals(scheme)) {
action = ACTION_SEARCH_KEYSERVER_FROM_URL;
} else {
// Android's Action when opening file associated to Keychain (see AndroidManifest.xml)
@@ -135,20 +132,24 @@ public class ImportKeysActivity extends BaseNfcActivity
action = ACTION_IMPORT_KEY;
}
}
+ if (action == null) {
+ // -> switch to default below
+ action = "";
+ }
switch (action) {
case ACTION_IMPORT_KEY: {
- /* Keychain's own Actions */
- startFileFragment(savedInstanceState);
-
if (dataUri != null) {
// action: directly load data
- startListFragment(savedInstanceState, null, dataUri, null, null);
+ startListFragment(null, dataUri, null, null);
} else if (extras.containsKey(EXTRA_KEY_BYTES)) {
byte[] importData = extras.getByteArray(EXTRA_KEY_BYTES);
// action: directly load data
- startListFragment(savedInstanceState, importData, null, null, null);
+ startListFragment(importData, null, null, null);
+ } else {
+ startTopFileFragment();
+ startListFragment(null, null, null, null);
}
break;
}
@@ -156,10 +157,6 @@ public class ImportKeysActivity extends BaseNfcActivity
case ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE:
case ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT: {
- // only used for OpenPgpService
- if (extras.containsKey(EXTRA_PENDING_INTENT_DATA)) {
- mPendingIntentData = extras.getParcelable(EXTRA_PENDING_INTENT_DATA);
- }
if (extras.containsKey(EXTRA_QUERY) || extras.containsKey(EXTRA_KEY_ID)) {
/* simple search based on query or key id */
@@ -175,10 +172,10 @@ public class ImportKeysActivity extends BaseNfcActivity
if (query != null && query.length() > 0) {
// display keyserver fragment with query
- startCloudFragment(savedInstanceState, query, false, null);
+ startTopCloudFragment(query, false, null);
// action: search immediately
- startListFragment(savedInstanceState, null, null, query, null);
+ startListFragment(null, null, query, null);
} else {
Log.e(Constants.TAG, "Query is empty!");
return;
@@ -194,10 +191,10 @@ public class ImportKeysActivity extends BaseNfcActivity
String query = "0x" + fingerprint;
// display keyserver fragment with query
- startCloudFragment(savedInstanceState, query, true, null);
+ startTopCloudFragment(query, true, null);
// action: search immediately
- startListFragment(savedInstanceState, null, null, query, null);
+ startListFragment(null, null, query, null);
}
} else {
Log.e(Constants.TAG,
@@ -208,14 +205,6 @@ public class ImportKeysActivity extends BaseNfcActivity
}
break;
}
- case ACTION_IMPORT_KEY_FROM_FILE: {
- // NOTE: this only displays the appropriate fragment, no actions are taken
- startFileFragment(savedInstanceState);
-
- // no immediate actions!
- startListFragment(savedInstanceState, null, null, null, null);
- break;
- }
case ACTION_SEARCH_KEYSERVER_FROM_URL: {
// need to process URL to get search query and keyserver authority
String query = dataUri.getQueryParameter("search");
@@ -223,120 +212,88 @@ public class ImportKeysActivity extends BaseNfcActivity
// if query not specified, we still allow users to search the keyserver in the link
if (query == null) {
Notify.create(this, R.string.import_url_warn_no_search_parameter, Notify.LENGTH_INDEFINITE,
- Notify.Style.WARN).show(mTopFragment);
+ Notify.Style.WARN).show();
// we just set the keyserver
- startCloudFragment(savedInstanceState, null, false, keyserver);
+ startTopCloudFragment(null, false, keyserver);
// we don't set the keyserver for ImportKeysListFragment since
// it'll be set in the cloudSearchPrefs of ImportKeysCloudFragment
// which is used when the user clicks on the search button
- startListFragment(savedInstanceState, null, null, null, null);
+ startListFragment(null, null, null, null);
} else {
// we allow our users to edit the query if they wish
- startCloudFragment(savedInstanceState, query, false, keyserver);
+ startTopCloudFragment(query, false, keyserver);
// search immediately
- startListFragment(savedInstanceState, null, null, query, keyserver);
+ startListFragment(null, null, query, keyserver);
}
break;
}
+ case ACTION_IMPORT_KEY_FROM_FILE:
case ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN: {
// NOTE: this only displays the appropriate fragment, no actions are taken
- startFileFragment(savedInstanceState);
-
- // no immediate actions!
- startListFragment(savedInstanceState, null, null, null, null);
+ startTopFileFragment();
+ startListFragment(null, null, null, null);
break;
}
default: {
- startCloudFragment(savedInstanceState, null, false, null);
- startListFragment(savedInstanceState, null, null, null, null);
+ startTopCloudFragment(null, false, null);
+ startListFragment(null, null, null, null);
break;
}
}
}
+ @Override
+ protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+
+ // the only thing we need to take care of for restoring state is
+ // that the top layout is shown iff it contains a fragment
+ Fragment topFragment = getSupportFragmentManager().findFragmentByTag(TAG_FRAG_TOP);
+ boolean hasTopFragment = topFragment != null;
+ findViewById(R.id.import_keys_top_layout).setVisibility(hasTopFragment ? View.VISIBLE : View.GONE);
+ }
/**
* if the fragment is started with non-null bytes/dataUri/serverQuery, it will immediately
* load content
*
- * @param savedInstanceState
* @param bytes bytes containing list of keyrings to import
* @param dataUri uri to file to import keyrings from
* @param serverQuery query to search for on the keyserver
* @param keyserver keyserver authority to search on. If null will use keyserver from
* user preferences
*/
- private void startListFragment(Bundle savedInstanceState, byte[] bytes, Uri dataUri,
- String serverQuery, String keyserver) {
- // However, if we're being restored from a previous state,
- // then we don't need to do anything and should return or else
- // we could end up with overlapping fragments.
- if (mListFragment != null) {
- return;
- }
-
- mListFragment = ImportKeysListFragment.newInstance(bytes, dataUri, serverQuery, false,
- keyserver);
-
- // Add the fragment to the 'fragment_container' FrameLayout
- // NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
+ private void startListFragment(byte[] bytes, Uri dataUri, String serverQuery, String keyserver) {
+ Fragment listFragment =
+ ImportKeysListFragment.newInstance(bytes, dataUri, serverQuery, false, keyserver);
getSupportFragmentManager().beginTransaction()
- .replace(R.id.import_keys_list_container, mListFragment)
- .commitAllowingStateLoss();
- // do it immediately!
- getSupportFragmentManager().executePendingTransactions();
+ .replace(R.id.import_keys_list_container, listFragment, TAG_FRAG_LIST)
+ .commit();
}
- private void startFileFragment(Bundle savedInstanceState) {
- // However, if we're being restored from a previous state,
- // then we don't need to do anything and should return or else
- // we could end up with overlapping fragments.
- if (mTopFragment != null) {
- return;
- }
-
- // Create an instance of the fragment
- mTopFragment = ImportKeysFileFragment.newInstance();
-
- // Add the fragment to the 'fragment_container' FrameLayout
- // NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
+ private void startTopFileFragment() {
+ findViewById(R.id.import_keys_top_layout).setVisibility(View.VISIBLE);
+ Fragment importFileFragment = ImportKeysFileFragment.newInstance();
getSupportFragmentManager().beginTransaction()
- .replace(R.id.import_keys_top_container, mTopFragment)
- .commitAllowingStateLoss();
- // do it immediately!
- getSupportFragmentManager().executePendingTransactions();
+ .replace(R.id.import_keys_top_container, importFileFragment, TAG_FRAG_TOP)
+ .commit();
}
/**
* loads the CloudFragment, which consists of the search bar, search button and settings icon
* visually.
*
- * @param savedInstanceState
* @param query search query
* @param disableQueryEdit if true, user will not be able to edit the search query
* @param keyserver keyserver authority to use for search, if null will use keyserver
* specified in user preferences
*/
-
- private void startCloudFragment(Bundle savedInstanceState, String query, boolean disableQueryEdit, String
- keyserver) {
- // However, if we're being restored from a previous state,
- // then we don't need to do anything and should return or else
- // we could end up with overlapping fragments.
- if (mTopFragment != null) {
- return;
- }
-
- // Create an instance of the fragment
- mTopFragment = ImportKeysCloudFragment.newInstance(query, disableQueryEdit, keyserver);
-
- // Add the fragment to the 'fragment_container' FrameLayout
- // NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
+ private void startTopCloudFragment(String query, boolean disableQueryEdit, String keyserver) {
+ findViewById(R.id.import_keys_top_layout).setVisibility(View.VISIBLE);
+ Fragment importCloudFragment = ImportKeysCloudFragment.newInstance(query, disableQueryEdit, keyserver);
getSupportFragmentManager().beginTransaction()
- .replace(R.id.import_keys_top_container, mTopFragment)
- .commitAllowingStateLoss();
- // do it immediately!
- getSupportFragmentManager().executePendingTransactions();
+ .replace(R.id.import_keys_top_container, importCloudFragment, TAG_FRAG_TOP)
+ .commit();
}
private boolean isFingerprintValid(String fingerprint) {
@@ -350,63 +307,32 @@ public class ImportKeysActivity extends BaseNfcActivity
}
public void loadCallback(final ImportKeysListFragment.LoaderState loaderState) {
- mListFragment.loadNew(loaderState);
+ FragmentManager fragMan = getSupportFragmentManager();
+ ImportKeysListFragment keyListFragment = (ImportKeysListFragment) fragMan.findFragmentByTag(TAG_FRAG_LIST);
+ keyListFragment.loadNew(loaderState);
}
- private void handleMessage(Message message) {
- if (message.arg1 == ServiceProgressHandler.MessageStatus.OKAY.ordinal()) {
- // get returned data bundle
- Bundle returnData = message.getData();
- if (returnData == null) {
- return;
- }
- final ImportKeyResult result =
- returnData.getParcelable(OperationResult.EXTRA_RESULT);
- if (result == null) {
- Log.e(Constants.TAG, "result == null");
- return;
- }
+ private void importSelectedKeys() {
- if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT.equals(getIntent().getAction())
- || ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN.equals(getIntent().getAction())) {
- Intent intent = new Intent();
- intent.putExtra(ImportKeyResult.EXTRA_RESULT, result);
- ImportKeysActivity.this.setResult(RESULT_OK, intent);
- ImportKeysActivity.this.finish();
- return;
- }
- if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE.equals(getIntent().getAction())) {
- ImportKeysActivity.this.setResult(RESULT_OK, mPendingIntentData);
- ImportKeysActivity.this.finish();
- return;
- }
-
- result.createNotify(ImportKeysActivity.this)
- .show((ViewGroup) findViewById(R.id.import_snackbar));
- }
- }
+ FragmentManager fragMan = getSupportFragmentManager();
+ ImportKeysListFragment keyListFragment = (ImportKeysListFragment) fragMan.findFragmentByTag(TAG_FRAG_LIST);
- /**
- * Import keys with mImportData
- */
- public void importKeys() {
-
- if (mListFragment.getSelectedEntries().size() == 0) {
+ if (keyListFragment.getSelectedEntries().size() == 0) {
Notify.create(this, R.string.error_nothing_import_selected, Notify.Style.ERROR)
.show((ViewGroup) findViewById(R.id.import_snackbar));
return;
}
- mOperationHelper = new CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult>(
+ mOperationHelper = new CryptoOperationHelper<>(
1, this, this, R.string.progress_importing
);
- ImportKeysListFragment.LoaderState ls = mListFragment.getLoaderState();
+ ImportKeysListFragment.LoaderState ls = keyListFragment.getLoaderState();
if (ls instanceof ImportKeysListFragment.BytesLoaderState) {
Log.d(Constants.TAG, "importKeys started");
// get DATA from selected key entries
- IteratorWithSize<ParcelableKeyRing> selectedEntries = mListFragment.getSelectedData();
+ IteratorWithSize<ParcelableKeyRing> selectedEntries = keyListFragment.getSelectedData();
// instead of giving the entries by Intent extra, cache them into a
// file to prevent Java Binder problems on heavy imports
@@ -435,7 +361,7 @@ public class ImportKeysActivity extends BaseNfcActivity
ArrayList<ParcelableKeyRing> keys = new ArrayList<>();
{
// change the format into ParcelableKeyRing
- ArrayList<ImportKeysListEntry> entries = mListFragment.getSelectedEntries();
+ ArrayList<ImportKeysListEntry> entries = keyListFragment.getSelectedEntries();
for (ImportKeysListEntry entry : entries) {
keys.add(new ParcelableKeyRing(
entry.getFingerprintHex(), entry.getKeyIdHex(), entry.getExtraData())
@@ -451,31 +377,35 @@ public class ImportKeysActivity extends BaseNfcActivity
}
@Override
- protected void onNfcPostExecute() throws IOException {
+ protected void onNfcPostExecute() {
// either way, finish after NFC AsyncTask
finish();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (mOperationHelper == null ||
- !mOperationHelper.handleActivityResult(requestCode, resultCode, data)) {
- super.onActivityResult(requestCode, resultCode, data);
+ if (mOperationHelper != null &&
+ mOperationHelper.handleActivityResult(requestCode, resultCode, data)) {
+ return;
}
+ super.onActivityResult(requestCode, resultCode, data);
}
public void handleResult(ImportKeyResult result) {
- if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT.equals(getIntent().getAction())
- || ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN.equals(getIntent().getAction())) {
+ String intentAction = getIntent().getAction();
+
+ if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT.equals(intentAction)
+ || ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN.equals(intentAction)) {
Intent intent = new Intent();
intent.putExtra(ImportKeyResult.EXTRA_RESULT, result);
- ImportKeysActivity.this.setResult(RESULT_OK, intent);
- ImportKeysActivity.this.finish();
+ setResult(RESULT_OK, intent);
+ finish();
return;
}
- if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE.equals(getIntent().getAction())) {
- ImportKeysActivity.this.setResult(RESULT_OK, mPendingIntentData);
- ImportKeysActivity.this.finish();
+
+ if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE.equals(intentAction)) {
+ setResult(RESULT_OK, mPendingIntentData);
+ finish();
return;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java
index 746c75600..8de60dfd3 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java
@@ -29,6 +29,9 @@ import android.view.ViewGroup;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
+import org.sufficientlysecure.keychain.pgp.PgpHelper;
+import org.sufficientlysecure.keychain.ui.util.Notify;
+import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.util.FileHelper;
public class ImportKeysFileFragment extends Fragment {
@@ -78,12 +81,16 @@ public class ImportKeysFileFragment extends Fragment {
String sendText = "";
if (clipboardText != null) {
sendText = clipboardText.toString();
+ sendText = PgpHelper.getPgpKeyContent(sendText);
+ if (sendText == null) {
+ Notify.create(mImportActivity, "Bad data!", Style.ERROR).show();
+ return;
+ }
mImportActivity.loadCallback(new ImportKeysListFragment.BytesLoaderState(sendText.getBytes(), null));
}
}
});
-
return view;
}
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 8502798cd..7aed3176c 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java
@@ -17,6 +17,11 @@
package org.sufficientlysecure.keychain.ui;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
import android.app.Activity;
import android.net.Uri;
import android.os.Bundle;
@@ -40,21 +45,12 @@ import org.sufficientlysecure.keychain.ui.adapter.AsyncTaskResultWrapper;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysAdapter;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListCloudLoader;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListLoader;
-import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize;
import org.sufficientlysecure.keychain.util.ParcelableProxy;
import org.sufficientlysecure.keychain.util.Preferences;
import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
-import java.io.ByteArrayInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-
public class ImportKeysListFragment extends ListFragment implements
LoaderManager.LoaderCallbacks<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> {
@@ -179,8 +175,8 @@ public class ImportKeysListFragment extends ListFragment implements
}
static public class BytesLoaderState extends LoaderState {
- byte[] mKeyBytes;
- Uri mDataUri;
+ public byte[] mKeyBytes;
+ public Uri mDataUri;
BytesLoaderState(byte[] keyBytes, Uri dataUri) {
mKeyBytes = keyBytes;
@@ -304,9 +300,7 @@ public class ImportKeysListFragment extends ListFragment implements
onCreateLoader(int id, Bundle args) {
switch (id) {
case LOADER_ID_BYTES: {
- BytesLoaderState ls = (BytesLoaderState) mLoaderState;
- InputData inputData = getInputData(ls.mKeyBytes, ls.mDataUri);
- return new ImportKeysListLoader(mActivity, inputData);
+ return new ImportKeysListLoader(mActivity, (BytesLoaderState) mLoaderState);
}
case LOADER_ID_CLOUD: {
CloudLoaderState ls = (CloudLoaderState) mLoaderState;
@@ -431,24 +425,4 @@ public class ImportKeysListFragment extends ListFragment implements
}
}
- private InputData getInputData(byte[] importBytes, Uri dataUri) {
- InputData inputData = null;
- if (importBytes != null) {
- inputData = new InputData(new ByteArrayInputStream(importBytes), importBytes.length);
- } else if (dataUri != null) {
- try {
- InputStream inputStream = getActivity().getContentResolver().openInputStream(dataUri);
- int length = inputStream.available();
-
- inputData = new InputData(inputStream, length);
- } catch (FileNotFoundException e) {
- Log.e(Constants.TAG, "FileNotFoundException!", e);
- } catch (IOException e) {
- Log.e(Constants.TAG, "IOException!", e);
- }
- }
-
- return inputData;
- }
-
}
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 b60f3984c..45ce604c3 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java
@@ -87,12 +87,7 @@ public class ImportKeysProxyActivity extends FragmentActivity
processScannedContent(dataUri);
} else if (ACTION_SCAN_WITH_RESULT.equals(action)
|| ACTION_SCAN_IMPORT.equals(action) || ACTION_QR_CODE_API.equals(action)) {
- IntentIntegrator integrator = new IntentIntegrator(this);
- integrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE_TYPES)
- .setPrompt(getString(R.string.import_qr_code_text))
- .setResultDisplayDuration(0);
- integrator.setOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
- integrator.initiateScan();
+ new IntentIntegrator(this).setCaptureActivity(QrCodeCaptureActivity.class).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/KeyListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java
index 2b6d786d4..db31bd0a1 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java
@@ -30,6 +30,7 @@ import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
@@ -46,14 +47,17 @@ import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.AdapterView;
+import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
+import android.widget.ViewAnimator;
import com.getbase.floatingactionbutton.FloatingActionButton;
import com.getbase.floatingactionbutton.FloatingActionsMenu;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
+import org.sufficientlysecure.keychain.operations.results.BenchmarkResult;
import org.sufficientlysecure.keychain.operations.results.ConsolidateResult;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
@@ -61,6 +65,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.service.BenchmarkInputParcel;
import org.sufficientlysecure.keychain.service.ConsolidateInputParcel;
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
@@ -98,6 +103,8 @@ public class KeyListFragment extends LoaderFragment
// saves the mode object for multiselect, needed for reset at some point
private ActionMode mActionMode = null;
+ private Button vSearchButton;
+ private ViewAnimator vSearchContainer;
private String mQuery;
private FloatingActionsMenu mFab;
@@ -162,7 +169,9 @@ public class KeyListFragment extends LoaderFragment
super.onActivityCreated(savedInstanceState);
// show app name instead of "keys" from nav drawer
- getActivity().setTitle(R.string.app_name);
+ final FragmentActivity activity = getActivity();
+
+ activity.setTitle(R.string.app_name);
mStickyList.setOnItemClickListener(this);
mStickyList.setAreHeadersSticky(true);
@@ -171,7 +180,7 @@ public class KeyListFragment extends LoaderFragment
// Adds an empty footer view so that the Floating Action Button won't block content
// in last few rows.
- View footer = new View(getActivity());
+ View footer = new View(activity);
int spacing = (int) android.util.TypedValue.applyDimension(
android.util.TypedValue.COMPLEX_UNIT_DIP, 72, getResources().getDisplayMetrics()
@@ -195,7 +204,7 @@ public class KeyListFragment extends LoaderFragment
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- android.view.MenuInflater inflater = getActivity().getMenuInflater();
+ android.view.MenuInflater inflater = activity.getMenuInflater();
inflater.inflate(R.menu.key_list_multi, menu);
mActionMode = mode;
return true;
@@ -235,7 +244,7 @@ public class KeyListFragment extends LoaderFragment
@Override
public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
- boolean checked) {
+ boolean checked) {
if (checked) {
mAdapter.setNewSelection(position, true);
} else {
@@ -255,8 +264,21 @@ public class KeyListFragment extends LoaderFragment
// Start out with a progress indicator.
setContentShown(false);
+ // this view is made visible if no data is available
+ mStickyList.setEmptyView(activity.findViewById(R.id.key_list_empty));
+
+ // click on search button (in empty view) starts query for search string
+ vSearchContainer = (ViewAnimator) activity.findViewById(R.id.search_container);
+ vSearchButton = (Button) activity.findViewById(R.id.search_button);
+ vSearchButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ startSearchForQuery();
+ }
+ });
+
// Create an empty adapter we will use to display the loaded data.
- mAdapter = new KeyListAdapter(getActivity(), null, 0);
+ mAdapter = new KeyListAdapter(activity, null, 0);
mStickyList.setAdapter(mAdapter);
// Prepare the loader. Either re-connect with an existing one,
@@ -264,8 +286,20 @@ public class KeyListFragment extends LoaderFragment
getLoaderManager().initLoader(0, null, this);
}
+ private void startSearchForQuery() {
+ Activity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
+
+ Intent searchIntent = new Intent(activity, ImportKeysActivity.class);
+ searchIntent.putExtra(ImportKeysActivity.EXTRA_QUERY, mQuery);
+ searchIntent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER);
+ startActivity(searchIntent);
+ }
+
static final String ORDER =
- KeyRings.HAS_ANY_SECRET + " DESC, UPPER(" + KeyRings.USER_ID + ") ASC";
+ KeyRings.HAS_ANY_SECRET + " DESC, " + KeyRings.USER_ID + " COLLATE NOCASE ASC";
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
@@ -319,9 +353,6 @@ public class KeyListFragment extends LoaderFragment
mStickyList.setAdapter(mAdapter);
- // this view is made visible if no data is available
- mStickyList.setEmptyView(getActivity().findViewById(R.id.key_list_empty));
-
// end action mode, if any
if (mActionMode != null) {
mActionMode.finish();
@@ -387,6 +418,7 @@ public class KeyListFragment extends LoaderFragment
if (Constants.DEBUG) {
menu.findItem(R.id.menu_key_list_debug_cons).setVisible(true);
+ menu.findItem(R.id.menu_key_list_debug_bench).setVisible(true);
menu.findItem(R.id.menu_key_list_debug_read).setVisible(true);
menu.findItem(R.id.menu_key_list_debug_write).setVisible(true);
menu.findItem(R.id.menu_key_list_debug_first_time).setVisible(true);
@@ -470,6 +502,10 @@ public class KeyListFragment extends LoaderFragment
consolidate();
return true;
+ case R.id.menu_key_list_debug_bench:
+ benchmark();
+ return true;
+
default:
return super.onOptionsItemSelected(item);
}
@@ -483,17 +519,25 @@ public class KeyListFragment extends LoaderFragment
@Override
public boolean onQueryTextChange(String s) {
Log.d(Constants.TAG, "onQueryTextChange s:" + s);
- // Called when the action bar search text has changed. Update
- // the search filter, and restart the loader to do a new query
- // with this filter.
+ // Called when the action bar search text has changed. Update the
+ // search filter, and restart the loader to do a new query with this
+ // filter.
// If the nav drawer is opened, onQueryTextChange("") is executed.
// This hack prevents restarting the loader.
- // TODO: better way to fix this?
- String tmp = (mQuery == null) ? "" : mQuery;
- if (!s.equals(tmp)) {
+ if (!s.equals(mQuery)) {
mQuery = s;
getLoaderManager().restartLoader(0, null, this);
}
+
+ if (s.length() > 2) {
+ vSearchButton.setText(getString(R.string.btn_search_for_query, mQuery));
+ vSearchContainer.setDisplayedChild(1);
+ vSearchContainer.setVisibility(View.VISIBLE);
+ } else {
+ vSearchContainer.setDisplayedChild(0);
+ vSearchContainer.setVisibility(View.GONE);
+ }
+
return true;
}
@@ -560,8 +604,8 @@ public class KeyListFragment extends LoaderFragment
mKeyserver = cloudPrefs.keyserver;
}
- mImportOpHelper = new CryptoOperationHelper<>(1, this,
- this, R.string.progress_updating);
+ mImportOpHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_updating);
+ mImportOpHelper.setProgressCancellable(true);
mImportOpHelper.cryptoOperation();
}
@@ -602,6 +646,43 @@ public class KeyListFragment extends LoaderFragment
mConsolidateOpHelper.cryptoOperation();
}
+ private void benchmark() {
+
+ CryptoOperationHelper.Callback<BenchmarkInputParcel, BenchmarkResult> callback
+ = new CryptoOperationHelper.Callback<BenchmarkInputParcel, BenchmarkResult>() {
+
+ @Override
+ public BenchmarkInputParcel createOperationInput() {
+ return new BenchmarkInputParcel(); // we want to perform a full consolidate
+ }
+
+ @Override
+ public void onCryptoOperationSuccess(BenchmarkResult result) {
+ result.createNotify(getActivity()).show();
+ }
+
+ @Override
+ public void onCryptoOperationCancelled() {
+
+ }
+
+ @Override
+ public void onCryptoOperationError(BenchmarkResult result) {
+ result.createNotify(getActivity()).show();
+ }
+
+ @Override
+ public boolean onCryptoSetProgress(String msg, int progress, int max) {
+ return false;
+ }
+ };
+
+ CryptoOperationHelper opHelper =
+ new CryptoOperationHelper<>(2, this, callback, R.string.progress_importing);
+
+ opHelper.cryptoOperation();
+ }
+
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (mImportOpHelper != null) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java
index 43c8d2643..411dac3ef 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java
@@ -43,7 +43,8 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEntryParcel;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogLevel;
import org.sufficientlysecure.keychain.operations.results.OperationResult.SubLogEntryParcel;
-import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
+import org.sufficientlysecure.keychain.provider.TemporaryFileProvider;
+import org.sufficientlysecure.keychain.ui.dialog.ShareLogDialogFragment;
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
@@ -139,7 +140,7 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe
// if there is no log temp file yet, create one
if (mLogTempFile == null) {
- mLogTempFile = TemporaryStorageProvider.createFile(getActivity(), "openkeychain_log.txt", "text/plain");
+ mLogTempFile = TemporaryFileProvider.createFile(getActivity(), "openkeychain_log.txt", "text/plain");
try {
OutputStream outputStream = activity.getContentResolver().openOutputStream(mLogTempFile);
outputStream.write(log.getBytes());
@@ -149,11 +150,9 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe
}
}
- Intent intent = new Intent(Intent.ACTION_SEND);
- intent.putExtra(Intent.EXTRA_STREAM, mLogTempFile);
- intent.setType("text/plain");
- intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- startActivity(intent);
+
+ ShareLogDialogFragment shareLogDialog = ShareLogDialogFragment.newInstance(mLogTempFile);
+ shareLogDialog.show(getActivity().getSupportFragmentManager(), "shareLogDialog");
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java
index 6f5d98afd..7bd7bafcc 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.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) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
* Copyright (C) 2015 Kai Jiang <jiangkai@gmail.com>
*
@@ -27,13 +27,13 @@ import android.support.v4.app.FragmentManager.OnBackStackChangedListener;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.widget.Toolbar;
import android.view.View;
-import android.widget.AdapterView;
import com.mikepenz.community_material_typeface_library.CommunityMaterial;
+import com.mikepenz.fontawesome_typeface_library.FontAwesome;
import com.mikepenz.google_material_typeface_library.GoogleMaterial;
-import com.mikepenz.iconics.typeface.FontAwesome;
import com.mikepenz.materialdrawer.Drawer;
import com.mikepenz.materialdrawer.DrawerBuilder;
+import com.mikepenz.materialdrawer.model.DividerDrawerItem;
import com.mikepenz.materialdrawer.model.PrimaryDrawerItem;
import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem;
@@ -75,25 +75,23 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac
.withToolbar(mToolbar)
.addDrawerItems(
new PrimaryDrawerItem().withName(R.string.nav_keys).withIcon(CommunityMaterial.Icon.cmd_key)
- .withIdentifier(ID_KEYS).withCheckable(false),
+ .withIdentifier(ID_KEYS).withSelectable(false),
new PrimaryDrawerItem().withName(R.string.nav_encrypt_decrypt).withIcon(FontAwesome.Icon.faw_lock)
- .withIdentifier(ID_ENCRYPT_DECRYPT).withCheckable(false),
+ .withIdentifier(ID_ENCRYPT_DECRYPT).withSelectable(false),
new PrimaryDrawerItem().withName(R.string.title_api_registered_apps).withIcon(CommunityMaterial.Icon.cmd_apps)
- .withIdentifier(ID_APPS).withCheckable(false),
+ .withIdentifier(ID_APPS).withSelectable(false),
new PrimaryDrawerItem().withName(R.string.nav_backup).withIcon(CommunityMaterial.Icon.cmd_backup_restore)
- .withIdentifier(ID_BACKUP).withCheckable(false)
- )
- .addStickyDrawerItems(
- // display and stick on bottom of drawer
- new PrimaryDrawerItem().withName(R.string.menu_preferences).withIcon(GoogleMaterial.Icon.gmd_settings).withIdentifier(ID_SETTINGS).withCheckable(false),
- new PrimaryDrawerItem().withName(R.string.menu_help).withIcon(CommunityMaterial.Icon.cmd_help_circle).withIdentifier(ID_HELP).withCheckable(false)
+ .withIdentifier(ID_BACKUP).withSelectable(false),
+ new DividerDrawerItem(),
+ new PrimaryDrawerItem().withName(R.string.menu_preferences).withIcon(GoogleMaterial.Icon.gmd_settings).withIdentifier(ID_SETTINGS).withSelectable(false),
+ new PrimaryDrawerItem().withName(R.string.menu_help).withIcon(CommunityMaterial.Icon.cmd_help_circle).withIdentifier(ID_HELP).withSelectable(false)
)
.withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() {
@Override
- public boolean onItemClick(AdapterView<?> parent, View view, int position, long id, IDrawerItem drawerItem) {
+ public boolean onItemClick(View view, int position, IDrawerItem drawerItem) {
if (drawerItem != null) {
Intent intent = null;
- switch(drawerItem.getIdentifier()) {
+ switch (drawerItem.getIdentifier()) {
case ID_KEYS:
onKeysSelected();
break;
@@ -182,29 +180,29 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac
private void onKeysSelected() {
mToolbar.setTitle(R.string.app_name);
- mDrawer.setSelectionByIdentifier(ID_KEYS, false);
+ mDrawer.setSelection(ID_KEYS, false);
Fragment frag = new KeyListFragment();
setFragment(frag, false);
}
private void onEnDecryptSelected() {
mToolbar.setTitle(R.string.nav_encrypt_decrypt);
- mDrawer.setSelectionByIdentifier(ID_ENCRYPT_DECRYPT, false);
- Fragment frag = new EncryptDecryptOverviewFragment();
+ mDrawer.setSelection(ID_ENCRYPT_DECRYPT, false);
+ Fragment frag = new EncryptDecryptFragment();
setFragment(frag, true);
}
private void onAppsSelected() {
mToolbar.setTitle(R.string.nav_apps);
- mDrawer.setSelectionByIdentifier(ID_APPS, false);
+ mDrawer.setSelection(ID_APPS, false);
Fragment frag = new AppsListFragment();
setFragment(frag, true);
}
private void onBackupSelected() {
mToolbar.setTitle(R.string.nav_backup);
- mDrawer.setSelectionByIdentifier(ID_APPS, false);
- Fragment frag = new BackupFragment();
+ mDrawer.setSelection(ID_BACKUP, false);
+ Fragment frag = new BackupRestoreFragment();
setFragment(frag, true);
}
@@ -258,16 +256,16 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac
// make sure the selected icon is the one shown at this point
if (frag instanceof KeyListFragment) {
mToolbar.setTitle(R.string.app_name);
- mDrawer.setSelection(mDrawer.getPositionFromIdentifier(ID_KEYS), false);
- } else if (frag instanceof EncryptDecryptOverviewFragment) {
+ mDrawer.setSelection(mDrawer.getPosition(ID_KEYS), false);
+ } else if (frag instanceof EncryptDecryptFragment) {
mToolbar.setTitle(R.string.nav_encrypt_decrypt);
- mDrawer.setSelection(mDrawer.getPositionFromIdentifier(ID_ENCRYPT_DECRYPT), false);
+ mDrawer.setSelection(mDrawer.getPosition(ID_ENCRYPT_DECRYPT), false);
} else if (frag instanceof AppsListFragment) {
mToolbar.setTitle(R.string.nav_apps);
- mDrawer.setSelection(mDrawer.getPositionFromIdentifier(ID_APPS), false);
- } else if (frag instanceof BackupFragment) {
+ mDrawer.setSelection(mDrawer.getPosition(ID_APPS), false);
+ } else if (frag instanceof BackupRestoreFragment) {
mToolbar.setTitle(R.string.nav_backup);
- mDrawer.setSelection(mDrawer.getPositionFromIdentifier(ID_BACKUP), false);
+ mDrawer.setSelection(mDrawer.getPosition(ID_BACKUP), false);
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java
index b811b218e..86b0a36d0 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java
@@ -27,6 +27,7 @@ import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.TextView;
+import android.widget.Toast;
import android.widget.ViewAnimator;
import org.sufficientlysecure.keychain.Constants;
@@ -72,7 +73,7 @@ public class NfcOperationActivity extends BaseNfcActivity {
private RequiredInputParcel mRequiredInput;
private Intent mServiceIntent;
- private static final byte[] BLANK_FINGERPRINT = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
+ private static final byte[] BLANK_FINGERPRINT = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
private CryptoInputParcel mInputParcel;
@@ -91,7 +92,9 @@ public class NfcOperationActivity extends BaseNfcActivity {
// prevent annoying orientation changes while fumbling with the device
OrientationUtils.lockOrientation(this);
-
+ // prevent close when touching outside of the dialog (happens easily when fumbling with the device)
+ setFinishOnTouchOutside(false);
+ // keep screen on
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
mInputParcel = getIntent().getParcelableExtra(EXTRA_CRYPTO_INPUT);
@@ -107,13 +110,18 @@ public class NfcOperationActivity extends BaseNfcActivity {
public void onClick(View v) {
resumeTagHandling();
- // obtain passphrase for this subkey
- if (mRequiredInput.mType != RequiredInputParcel.RequiredInputType.NFC_MOVE_KEY_TO_CARD) {
- obtainYubiKeyPin(mRequiredInput);
- }
+ obtainPassphraseIfRequired();
vAnimator.setDisplayedChild(0);
}
});
+ Button vCancel = (Button) findViewById(R.id.nfc_activity_0_cancel);
+ vCancel.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+ });
Intent intent = getIntent();
Bundle data = intent.getExtras();
@@ -121,8 +129,13 @@ public class NfcOperationActivity extends BaseNfcActivity {
mRequiredInput = data.getParcelable(EXTRA_REQUIRED_INPUT);
mServiceIntent = data.getParcelable(EXTRA_SERVICE_INTENT);
+ obtainPassphraseIfRequired();
+ }
+
+ private void obtainPassphraseIfRequired() {
// obtain passphrase for this subkey
- if (mRequiredInput.mType != RequiredInputParcel.RequiredInputType.NFC_MOVE_KEY_TO_CARD) {
+ if (mRequiredInput.mType != RequiredInputParcel.RequiredInputType.NFC_MOVE_KEY_TO_CARD
+ && mRequiredInput.mType != RequiredInputParcel.RequiredInputType.NFC_RESET_CARD) {
obtainYubiKeyPin(mRequiredInput);
}
}
@@ -237,6 +250,11 @@ public class NfcOperationActivity extends BaseNfcActivity {
break;
}
+ case NFC_RESET_CARD: {
+ nfcResetCard();
+
+ break;
+ }
default: {
throw new AssertionError("Unhandled mRequiredInput.mType");
}
@@ -245,7 +263,7 @@ public class NfcOperationActivity extends BaseNfcActivity {
}
@Override
- protected void onNfcPostExecute() throws IOException {
+ protected void onNfcPostExecute() {
if (mServiceIntent != null) {
// if we're triggered by OpenPgpService
// save updated cryptoInputParcel in cache
@@ -276,6 +294,7 @@ public class NfcOperationActivity extends BaseNfcActivity {
}
}
}
+
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
@@ -292,11 +311,27 @@ public class NfcOperationActivity extends BaseNfcActivity {
vAnimator.setDisplayedChild(3);
}
+ @Override
+ public void onNfcPinError(String error) {
+ onNfcError(error);
+
+ // clear (invalid) passphrase
+ PassphraseCacheService.clearCachedPassphrase(
+ this, mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId());
+ }
+
private boolean shouldPutKey(byte[] fingerprint, int idx) throws IOException {
- byte[] cardFingerprint = nfcGetFingerprint(idx);
+ byte[] cardFingerprint = nfcGetMasterKeyFingerprint(idx);
+
+ // Note: special case: This should not happen, but happens with
+ // https://github.com/FluffyKaon/OpenPGP-Card, thus for now assume true
+ if (cardFingerprint == null) {
+ return true;
+ }
+
// Slot is empty, or contains this key already. PUT KEY operation is safe
if (Arrays.equals(cardFingerprint, BLANK_FINGERPRINT) ||
- Arrays.equals(cardFingerprint, fingerprint)) {
+ Arrays.equals(cardFingerprint, fingerprint)) {
return true;
}
@@ -304,21 +339,4 @@ public class NfcOperationActivity extends BaseNfcActivity {
return false;
}
- @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());
- }
-
}
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 e71349880..c3a33fc92 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java
@@ -29,7 +29,9 @@ import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import android.support.v7.app.AlertDialog;
+import android.text.Editable;
import android.text.InputType;
+import android.text.TextWatcher;
import android.text.method.PasswordTransformationMethod;
import android.view.ContextThemeWrapper;
import android.view.KeyEvent;
@@ -117,6 +119,10 @@ public class PassphraseDialogActivity extends FragmentActivity {
mSubKeyId = Constants.key.symmetric;
break;
}
+ case BACKUP_CODE: {
+ mSubKeyId = Constants.key.backup_code;
+ break;
+ }
case PASSPHRASE: {
// handle empty passphrases by directly returning an empty crypto input parcel
@@ -186,6 +192,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
private EditText mPassphraseEditText;
private TextView mPassphraseText;
private View mInput, mProgress;
+ private EditText[] mBackupCodeEditText;
private CanonicalizedSecretKeyRing mSecretRing = null;
private boolean mIsCancelled = false;
@@ -208,6 +215,24 @@ public class PassphraseDialogActivity extends FragmentActivity {
// No title, see http://www.google.com/design/spec/components/dialogs.html#dialogs-alerts
//alert.setTitle()
+ if (mSubKeyId == Constants.key.backup_code) {
+ LayoutInflater inflater = LayoutInflater.from(theme);
+ View view = inflater.inflate(R.layout.passphrase_dialog_backup_code, null);
+ alert.setView(view);
+
+ mBackupCodeEditText = new EditText[4];
+ mBackupCodeEditText[0] = (EditText) view.findViewById(R.id.backup_code_1);
+ mBackupCodeEditText[1] = (EditText) view.findViewById(R.id.backup_code_2);
+ mBackupCodeEditText[2] = (EditText) view.findViewById(R.id.backup_code_3);
+ mBackupCodeEditText[3] = (EditText) view.findViewById(R.id.backup_code_4);
+ setupEditTextFocusNext(mBackupCodeEditText);
+
+ AlertDialog dialog = alert.create();
+ dialog.setButton(DialogInterface.BUTTON_POSITIVE,
+ activity.getString(R.string.btn_unlock), (DialogInterface.OnClickListener) null);
+ return dialog;
+ }
+
LayoutInflater inflater = LayoutInflater.from(theme);
View view = inflater.inflate(R.layout.passphrase_dialog, null);
alert.setView(view);
@@ -229,8 +254,10 @@ public class PassphraseDialogActivity extends FragmentActivity {
CanonicalizedSecretKey.SecretKeyType keyType = CanonicalizedSecretKey.SecretKeyType.PASSPHRASE;
String message;
+ String hint;
if (mSubKeyId == Constants.key.symmetric || mSubKeyId == Constants.key.none) {
message = getString(R.string.passphrase_for_symmetric_encryption);
+ hint = getString(R.string.label_passphrase);
} else {
try {
ProviderHelper helper = new ProviderHelper(activity);
@@ -255,12 +282,15 @@ public class PassphraseDialogActivity extends FragmentActivity {
switch (keyType) {
case PASSPHRASE:
message = getString(R.string.passphrase_for, userId);
+ hint = getString(R.string.label_passphrase);
break;
case PIN:
message = getString(R.string.pin_for, userId);
+ hint = getString(R.string.label_pin);
break;
case DIVERT_TO_CARD:
message = getString(R.string.yubikey_pin_for, userId);
+ hint = getString(R.string.label_pin);
break;
// special case: empty passphrase just returns the empty passphrase
case PASSPHRASE_EMPTY:
@@ -283,6 +313,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
}
mPassphraseText.setText(message);
+ mPassphraseEditText.setHint(hint);
// Hack to open keyboard.
// This is the only method that I found to work across all Android versions
@@ -327,6 +358,34 @@ public class PassphraseDialogActivity extends FragmentActivity {
return dialog;
}
+ private static void setupEditTextFocusNext(final EditText[] backupCodes) {
+ for (int i = 0; i < backupCodes.length - 1; i++) {
+
+ final int next = i + 1;
+
+ backupCodes[i].addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ boolean inserting = before < count;
+ boolean cursorAtEnd = (start + count) == 6;
+
+ if (inserting && cursorAtEnd) {
+ backupCodes[next].requestFocus();
+ }
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ }
+ });
+
+ }
+ }
+
@Override
public void onStart() {
super.onStart();
@@ -336,7 +395,23 @@ public class PassphraseDialogActivity extends FragmentActivity {
positive.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- final Passphrase passphrase = new Passphrase(mPassphraseEditText);
+
+ final Passphrase passphrase;
+ if (mSubKeyId == Constants.key.backup_code) {
+ StringBuilder backupCodeInput = new StringBuilder(26);
+ for (EditText editText : mBackupCodeEditText) {
+ if (editText.getText().length() < 6) {
+ return;
+ }
+ backupCodeInput.append(editText.getText());
+ backupCodeInput.append('-');
+ }
+ backupCodeInput.deleteCharAt(backupCodeInput.length() - 1);
+
+ passphrase = new Passphrase(backupCodeInput.toString());
+ } else {
+ passphrase = new Passphrase(mPassphraseEditText);
+ }
CryptoInputParcel cryptoInputParcel =
((PassphraseDialogActivity) getActivity()).mCryptoInputParcel;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeCaptureActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeCaptureActivity.java
new file mode 100644
index 000000000..b5d3948be
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/QrCodeCaptureActivity.java
@@ -0,0 +1,125 @@
+/*
+ * 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;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.content.ContextCompat;
+import android.view.KeyEvent;
+
+import com.journeyapps.barcodescanner.CaptureManager;
+import com.journeyapps.barcodescanner.CompoundBarcodeView;
+
+import org.sufficientlysecure.keychain.R;
+
+public class QrCodeCaptureActivity extends FragmentActivity {
+ private CaptureManager capture;
+ private CompoundBarcodeView barcodeScannerView;
+
+ public static final int MY_PERMISSIONS_REQUEST_CAMERA = 42;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.qr_code_capture_activity);
+
+ barcodeScannerView = (CompoundBarcodeView) findViewById(R.id.zxing_barcode_scanner);
+ barcodeScannerView.setStatusText(getString(R.string.import_qr_code_text));
+
+ if (savedInstanceState != null) {
+ init(barcodeScannerView, getIntent(), savedInstanceState);
+ }
+
+ // check Android 6 permission
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
+ == PackageManager.PERMISSION_GRANTED) {
+ init(barcodeScannerView, getIntent(), null);
+ } else {
+ ActivityCompat.requestPermissions(this,
+ new String[]{Manifest.permission.CAMERA},
+ MY_PERMISSIONS_REQUEST_CAMERA);
+ }
+ }
+
+ private void init(CompoundBarcodeView barcodeScannerView, Intent intent, Bundle savedInstanceState) {
+ capture = new CaptureManager(this, barcodeScannerView);
+ capture.initializeFromIntent(intent, savedInstanceState);
+ capture.decode();
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[],
+ @NonNull int[] grantResults) {
+ switch (requestCode) {
+ case MY_PERMISSIONS_REQUEST_CAMERA: {
+ if (grantResults.length > 0
+ && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ // permission was granted
+ init(barcodeScannerView, getIntent(), null);
+ } else {
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (capture != null) {
+ capture.onResume();
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ if (capture != null) {
+ capture.onPause();
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (capture != null) {
+ capture.onDestroy();
+ }
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ if (capture != null) {
+ capture.onSaveInstanceState(outState);
+ }
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ return barcodeScannerView.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
+ }
+} \ No newline at end of file
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 eb9ee05af..f5c239558 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java
@@ -54,14 +54,8 @@ import java.util.List;
public class SettingsActivity extends AppCompatPreferenceActivity {
- public static final String ACTION_PREFS_CLOUD = "org.sufficientlysecure.keychain.ui.PREFS_CLOUD";
- public static final String ACTION_PREFS_ADV = "org.sufficientlysecure.keychain.ui.PREFS_ADV";
- public static final String ACTION_PREFS_PROXY = "org.sufficientlysecure.keychain.ui.PREFS_PROXY";
- public static final String ACTION_PREFS_GUI = "org.sufficientlysecure.keychain.ui.PREFS_GUI";
-
public static final int REQUEST_CODE_KEYSERVER_PREF = 0x00007005;
- private PreferenceScreen mKeyServerPreference = null;
private static Preferences sPreferences;
private ThemeChanger mThemeChanger;
@@ -74,52 +68,6 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
super.onCreate(savedInstanceState);
setupToolbar();
-
- String action = getIntent().getAction();
-
- if (ACTION_PREFS_CLOUD.equals(action)) {
- addPreferencesFromResource(R.xml.cloud_search_prefs);
-
- mKeyServerPreference = (PreferenceScreen) findPreference(Constants.Pref.KEY_SERVERS);
- mKeyServerPreference.setSummary(keyserverSummary(this));
- mKeyServerPreference
- .setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
- public boolean onPreferenceClick(Preference preference) {
- Intent intent = new Intent(SettingsActivity.this,
- SettingsKeyServerActivity.class);
- intent.putExtra(SettingsKeyServerActivity.EXTRA_KEY_SERVERS,
- sPreferences.getKeyServers());
- startActivityForResult(intent, REQUEST_CODE_KEYSERVER_PREF);
- return false;
- }
- });
- initializeSearchKeyserver(
- (SwitchPreference) findPreference(Constants.Pref.SEARCH_KEYSERVER)
- );
- initializeSearchKeybase(
- (SwitchPreference) findPreference(Constants.Pref.SEARCH_KEYBASE)
- );
-
- } else if (ACTION_PREFS_ADV.equals(action)) {
- addPreferencesFromResource(R.xml.passphrase_preferences);
-
- initializePassphraseCacheSubs(
- (CheckBoxPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_SUBS));
-
- initializePassphraseCacheTtl(
- (IntegerListPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_TTL));
-
- initializeUseDefaultYubiKeyPin(
- (CheckBoxPreference) findPreference(Constants.Pref.USE_DEFAULT_YUBIKEY_PIN));
-
- initializeUseNumKeypadForYubiKeyPin(
- (CheckBoxPreference) findPreference(Constants.Pref.USE_NUMKEYPAD_FOR_YUBIKEY_PIN));
-
- } else if (ACTION_PREFS_GUI.equals(action)) {
- addPreferencesFromResource(R.xml.gui_preferences);
-
- initializeTheme((ListPreference) findPreference(Constants.Pref.THEME));
- }
}
@Override
@@ -237,9 +185,6 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
initializePassphraseCacheTtl(
(IntegerListPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_TTL));
- initializeUseDefaultYubiKeyPin(
- (CheckBoxPreference) findPreference(Constants.Pref.USE_DEFAULT_YUBIKEY_PIN));
-
initializeUseNumKeypadForYubiKeyPin(
(CheckBoxPreference) findPreference(Constants.Pref.USE_NUMKEYPAD_FOR_YUBIKEY_PIN));
}
@@ -454,23 +399,6 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
}
/**
- * This fragment shows gui preferences.
- */
- public static class GuiPrefsFragment extends PreferenceFragment {
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
-
- // Load the preferences from an XML resource
- addPreferencesFromResource(R.xml.gui_preferences);
-
- initializeTheme((ListPreference) findPreference(Constants.Pref.THEME));
- }
- }
-
- /**
* This fragment shows the keyserver/contacts sync preferences
*/
public static class SyncPrefsFragment extends PreferenceFragment {
@@ -582,7 +510,6 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
return PassphrasePrefsFragment.class.getName().equals(fragmentName)
|| CloudSearchPrefsFragment.class.getName().equals(fragmentName)
|| ProxyPrefsFragment.class.getName().equals(fragmentName)
- || GuiPrefsFragment.class.getName().equals(fragmentName)
|| SyncPrefsFragment.class.getName().equals(fragmentName)
|| ExperimentalPrefsFragment.class.getName().equals(fragmentName)
|| super.isValidFragment(fragmentName);
@@ -665,17 +592,6 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
.getPreferredKeyserver();
}
- 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);
- return false;
- }
- });
- }
-
private static void initializeUseNumKeypadForYubiKeyPin(final CheckBoxPreference mUseNumKeypadForYubiKeyPin) {
mUseNumKeypadForYubiKeyPin.setChecked(sPreferences.useNumKeypadForYubiKeyPin());
mUseNumKeypadForYubiKeyPin.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java
index d8edbe4f8..5a8ab36bc 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java
@@ -155,7 +155,7 @@ public class SettingsKeyserverFragment extends Fragment implements RecyclerItemC
data.getBoolean(AddEditKeyserverDialogFragment.MESSAGE_VERIFIED);
if (verified) {
Notify.create(getActivity(),
- R.string.add_keyserver_verified, Notify.Style.OK).show();
+ R.string.add_keyserver_connection_verified, Notify.Style.OK).show();
} else {
Notify.create(getActivity(),
R.string.add_keyserver_without_verification,
@@ -177,26 +177,6 @@ public class SettingsKeyserverFragment extends Fragment implements RecyclerItemC
}
break;
}
- case AddEditKeyserverDialogFragment.MESSAGE_VERIFICATION_FAILED: {
- AddEditKeyserverDialogFragment.FailureReason failureReason =
- (AddEditKeyserverDialogFragment.FailureReason) data.getSerializable(
- AddEditKeyserverDialogFragment.MESSAGE_FAILURE_REASON);
- switch (failureReason) {
- case CONNECTION_FAILED: {
- Notify.create(getActivity(),
- R.string.add_keyserver_connection_failed,
- Notify.Style.ERROR).show();
- break;
- }
- case INVALID_URL: {
- Notify.create(getActivity(),
- R.string.add_keyserver_invalid_url,
- Notify.Style.ERROR).show();
- break;
- }
- }
- break;
- }
}
}
};
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 0415128a2..f38e4928d 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java
@@ -17,6 +17,7 @@
package org.sufficientlysecure.keychain.ui;
+
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
@@ -29,10 +30,12 @@ import android.widget.Spinner;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.operations.results.ExportResult;
+import org.sufficientlysecure.keychain.operations.results.UploadResult;
+import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
-import org.sufficientlysecure.keychain.service.ExportKeyringParcel;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.service.UploadKeyringParcel;
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
import org.sufficientlysecure.keychain.util.Log;
@@ -42,7 +45,7 @@ import org.sufficientlysecure.keychain.util.Preferences;
* Sends the selected public key to a keyserver
*/
public class UploadKeyActivity extends BaseActivity
- implements CryptoOperationHelper.Callback<ExportKeyringParcel, ExportResult> {
+ implements CryptoOperationHelper.Callback<UploadKeyringParcel, UploadResult> {
private View mUploadButton;
private Spinner mKeyServerSpinner;
@@ -50,8 +53,8 @@ public class UploadKeyActivity extends BaseActivity
// CryptoOperationHelper.Callback vars
private String mKeyserver;
- private Uri mUnifiedKeyringUri;
- private CryptoOperationHelper<ExportKeyringParcel, ExportResult> mUploadOpHelper;
+ private long mMasterKeyId;
+ private CryptoOperationHelper<UploadKeyringParcel, UploadResult> mUploadOpHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -85,6 +88,16 @@ public class UploadKeyActivity extends BaseActivity
finish();
return;
}
+
+ try {
+ mMasterKeyId = new ProviderHelper(this).getCachedPublicKeyRing(
+ KeyRings.buildUnifiedKeyRingUri(mDataUri)).getMasterKeyId();
+ } catch (PgpKeyNotFoundException e) {
+ Log.e(Constants.TAG, "Intent data pointed to bad key!");
+ finish();
+ return;
+ }
+
}
@Override
@@ -101,13 +114,10 @@ public class UploadKeyActivity extends BaseActivity
}
private void uploadKey() {
- Uri blobUri = KeyRings.buildUnifiedKeyRingUri(mDataUri);
- mUnifiedKeyringUri = blobUri;
-
String server = (String) mKeyServerSpinner.getSelectedItem();
mKeyserver = server;
- mUploadOpHelper = new CryptoOperationHelper(1, this, this, R.string.progress_uploading);
+ mUploadOpHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_uploading);
mUploadOpHelper.cryptoOperation();
}
@@ -125,12 +135,12 @@ public class UploadKeyActivity extends BaseActivity
}
@Override
- public ExportKeyringParcel createOperationInput() {
- return new ExportKeyringParcel(mKeyserver, mUnifiedKeyringUri);
+ public UploadKeyringParcel createOperationInput() {
+ return new UploadKeyringParcel(mKeyserver, mMasterKeyId);
}
@Override
- public void onCryptoOperationSuccess(ExportResult result) {
+ public void onCryptoOperationSuccess(UploadResult result) {
result.createNotify(this).show();
}
@@ -140,7 +150,7 @@ public class UploadKeyActivity extends BaseActivity
}
@Override
- public void onCryptoOperationError(ExportResult result) {
+ public void onCryptoOperationError(UploadResult result) {
result.createNotify(this).show();
}
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 a09e74abe..0f538cd1b 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java
@@ -19,7 +19,6 @@
package org.sufficientlysecure.keychain.ui;
-import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
@@ -39,6 +38,7 @@ import android.os.Handler;
import android.provider.ContactsContract;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.CollapsingToolbarLayout;
+import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.FragmentManager;
@@ -65,12 +65,14 @@ import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
+import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
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.provider.ProviderHelper.NotFoundException;
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
import org.sufficientlysecure.keychain.ui.ViewKeyFragment.PostponeType;
import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
@@ -85,7 +87,6 @@ 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;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.NfcHelper;
import org.sufficientlysecure.keychain.util.Preferences;
@@ -355,7 +356,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements
startActivity(homeIntent);
return true;
}
- case R.id.menu_key_view_export_file: {
+ case R.id.menu_key_view_backup: {
startPassphraseActivity(REQUEST_BACKUP);
return true;
}
@@ -405,8 +406,8 @@ public class ViewKeyActivity extends BaseNfcActivity implements
MenuItem editKey = menu.findItem(R.id.menu_key_view_edit);
editKey.setVisible(mIsSecret);
- MenuItem exportKey = menu.findItem(R.id.menu_key_view_export_file);
- exportKey.setVisible(mIsSecret);
+ MenuItem backupKey = menu.findItem(R.id.menu_key_view_backup);
+ backupKey.setVisible(mIsSecret);
MenuItem addLinked = menu.findItem(R.id.menu_key_view_add_linked_identity);
addLinked.setVisible(mIsSecret
@@ -460,15 +461,40 @@ public class ViewKeyActivity extends BaseNfcActivity implements
}
private void startPassphraseActivity(int requestCode) {
- Intent intent = new Intent(this, PassphraseDialogActivity.class);
- intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, mMasterKeyId);
- startActivityForResult(intent, requestCode);
+
+ if (keyHasPassphrase()) {
+ Intent intent = new Intent(this, PassphraseDialogActivity.class);
+ intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, mMasterKeyId);
+ startActivityForResult(intent, requestCode);
+ } else {
+ startBackupActivity();
+ }
+ }
+
+ private boolean keyHasPassphrase() {
+ try {
+ SecretKeyType secretKeyType =
+ mProviderHelper.getCachedPublicKeyRing(mMasterKeyId).getSecretKeyType(mMasterKeyId);
+ switch (secretKeyType) {
+ // all of these make no sense to ask
+ case PASSPHRASE_EMPTY:
+ case GNU_DUMMY:
+ case DIVERT_TO_CARD:
+ case UNAVAILABLE:
+ return false;
+ default:
+ return true;
+ }
+ } catch (NotFoundException e) {
+ return false;
+ }
}
- private void backupToFile() {
- new ExportHelper(this).showExportKeysDialog(
- mMasterKeyId, new File(Constants.Path.APP_DIR,
- KeyFormattingUtils.convertKeyIdToHex(mMasterKeyId) + ".sec.asc"), true);
+ private void startBackupActivity() {
+ Intent intent = new Intent(this, BackupActivity.class);
+ intent.putExtra(BackupActivity.EXTRA_MASTER_KEY_IDS, new long[] { mMasterKeyId });
+ intent.putExtra(BackupActivity.EXTRA_SECRET, true);
+ startActivity(intent);
}
private void deleteKey() {
@@ -524,7 +550,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements
return;
}
- backupToFile();
+ startBackupActivity();
return;
}
@@ -564,7 +590,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements
}
@Override
- protected void onNfcPostExecute() throws IOException {
+ protected void onNfcPostExecute() {
long yubiKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mNfcFingerprints);
@@ -854,7 +880,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements
mActionEncryptFile.setVisibility(View.INVISIBLE);
mActionEncryptText.setVisibility(View.INVISIBLE);
mActionNfc.setVisibility(View.INVISIBLE);
- mFab.setVisibility(View.GONE);
+ hideFab();
mQrCodeLayout.setVisibility(View.GONE);
} else if (mIsExpired) {
if (mIsSecret) {
@@ -870,7 +896,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements
mActionEncryptFile.setVisibility(View.INVISIBLE);
mActionEncryptText.setVisibility(View.INVISIBLE);
mActionNfc.setVisibility(View.INVISIBLE);
- mFab.setVisibility(View.GONE);
+ hideFab();
mQrCodeLayout.setVisibility(View.GONE);
} else if (mIsSecret) {
mStatusText.setText(R.string.view_key_my_key);
@@ -912,7 +938,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements
} else {
mActionNfc.setVisibility(View.GONE);
}
- mFab.setVisibility(View.VISIBLE);
+ showFab();
// noinspection deprecation (no getDrawable with theme at current minApi level 15!)
mFab.setImageDrawable(getResources().getDrawable(R.drawable.ic_repeat_white_24dp));
} else {
@@ -929,7 +955,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements
color = getResources().getColor(R.color.key_flag_green);
photoTask.execute(mMasterKeyId);
- mFab.setVisibility(View.GONE);
+ hideFab();
} else {
mStatusText.setText(R.string.view_key_unverified);
mStatusImage.setVisibility(View.VISIBLE);
@@ -937,7 +963,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements
State.UNVERIFIED, R.color.icons, true);
color = getResources().getColor(R.color.key_flag_orange);
- mFab.setVisibility(View.VISIBLE);
+ showFab();
}
}
@@ -967,6 +993,28 @@ public class ViewKeyActivity extends BaseNfcActivity implements
}
}
+ /**
+ * Helper to show Fab, from http://stackoverflow.com/a/31047038
+ */
+ private void showFab() {
+ CoordinatorLayout.LayoutParams p = (CoordinatorLayout.LayoutParams) mFab.getLayoutParams();
+ p.setBehavior(new FloatingActionButton.Behavior());
+ p.setAnchorId(R.id.app_bar_layout);
+ mFab.setLayoutParams(p);
+ mFab.setVisibility(View.VISIBLE);
+ }
+
+ /**
+ * Helper to hide Fab, from http://stackoverflow.com/a/31047038
+ */
+ private void hideFab() {
+ CoordinatorLayout.LayoutParams p = (CoordinatorLayout.LayoutParams) mFab.getLayoutParams();
+ p.setBehavior(null); //should disable default animations
+ p.setAnchorId(View.NO_ID); //should let you set visibility
+ mFab.setLayoutParams(p);
+ mFab.setVisibility(View.GONE);
+ }
+
@Override
public void onLoaderReset(Loader<Cursor> loader) {
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 edd9feec9..bba6a6dc1 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java
@@ -41,13 +41,11 @@ 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;
import org.sufficientlysecure.keychain.util.Log;
public class ViewKeyAdvActivity extends BaseActivity implements
LoaderManager.LoaderCallbacks<Cursor> {
- ExportHelper mExportHelper;
ProviderHelper mProviderHelper;
protected Uri mDataUri;
@@ -75,7 +73,6 @@ public class ViewKeyAdvActivity extends BaseActivity implements
}
});
- mExportHelper = new ExportHelper(this);
mProviderHelper = new ProviderHelper(this);
mViewPager = (ViewPager) findViewById(R.id.pager);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java
index 4a46896bc..c5e575e32 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java
@@ -52,18 +52,18 @@ import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.pgp.KeyRing;
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.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
-import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
+import org.sufficientlysecure.keychain.provider.TemporaryFileProvider;
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
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.ui.util.QrCodeUtils;
-import org.sufficientlysecure.keychain.util.ExportHelper;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.NfcHelper;
@@ -84,7 +84,7 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
private Uri mDataUri;
private byte[] mFingerprint;
- private long mMasterKeyId;
+ private String mUserId;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
@@ -107,7 +107,6 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
View vFingerprintShareButton = view.findViewById(R.id.view_key_action_fingerprint_share);
View vFingerprintClipboardButton = view.findViewById(R.id.view_key_action_fingerprint_clipboard);
View vKeyShareButton = view.findViewById(R.id.view_key_action_key_share);
- View vKeySafeButton = view.findViewById(R.id.view_key_action_key_export);
View vKeyNfcButton = view.findViewById(R.id.view_key_action_key_nfc);
View vKeyClipboardButton = view.findViewById(R.id.view_key_action_key_clipboard);
ImageButton vKeySafeSlingerButton = (ImageButton) view.findViewById(R.id.view_key_action_key_safeslinger);
@@ -118,31 +117,25 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
vFingerprintShareButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- share(true, false);
+ shareFingerprint(false);
}
});
vFingerprintClipboardButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- share(true, true);
+ shareFingerprint(true);
}
});
vKeyShareButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- share(false, false);
- }
- });
- vKeySafeButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- exportToFile();
+ shareKey(false);
}
});
vKeyClipboardButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- share(false, true);
+ shareKey(true);
}
});
@@ -174,11 +167,6 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
return root;
}
- private void exportToFile() {
- new ExportHelper(getActivity()).showExportKeysDialog(
- mMasterKeyId, Constants.Path.APP_DIR_FILE, false);
- }
-
private void startSafeSlinger(Uri dataUri) {
long keyId = 0;
try {
@@ -193,7 +181,7 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
startActivityForResult(safeSlingerIntent, 0);
}
- private void share(boolean fingerprintOnly, boolean toClipboard) {
+ private void shareKey(boolean toClipboard) {
Activity activity = getActivity();
if (activity == null || mFingerprint == null) {
return;
@@ -201,18 +189,8 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
ProviderHelper providerHelper = new ProviderHelper(activity);
try {
- String content;
- if (fingerprintOnly) {
- String fingerprint = KeyFormattingUtils.convertFingerprintToHex(mFingerprint);
- if (!toClipboard) {
- content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint;
- } else {
- content = fingerprint;
- }
- } else {
- content = providerHelper.getKeyRingAsArmoredString(
- KeychainContract.KeyRingData.buildPublicKeyRingUri(mDataUri));
- }
+ String content = providerHelper.getKeyRingAsArmoredString(
+ KeychainContract.KeyRingData.buildPublicKeyRingUri(mDataUri));
if (toClipboard) {
ClipboardManager clipMan = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
@@ -224,29 +202,26 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
ClipData clip = ClipData.newPlainText(Constants.CLIPBOARD_LABEL, content);
clipMan.setPrimaryClip(clip);
- Notify.create(activity, fingerprintOnly ? R.string.fingerprint_copied_to_clipboard
- : R.string.key_copied_to_clipboard, Notify.Style.OK).show();
- return;
- }
-
- // Android will fail with android.os.TransactionTooLargeException if key is too big
- // see http://www.lonestarprod.com/?p=34
- if (content.length() >= 86389) {
- Notify.create(activity, R.string.key_too_big_for_sharing, Notify.Style.ERROR).show();
+ Notify.create(activity, R.string.key_copied_to_clipboard, Notify.Style.OK).show();
return;
}
// let user choose application
Intent sendIntent = new Intent(Intent.ACTION_SEND);
- sendIntent.putExtra(Intent.EXTRA_TEXT, content);
sendIntent.setType("text/plain");
- // Bluetooth Share will convert text/plain sent via EXTRA_TEXT to HTML
- // Add replacement extra to send a text/plain file instead.
+ // NOTE: Don't use Intent.EXTRA_TEXT to send the key
+ // better send it via a Uri!
+ // example: Bluetooth Share will convert text/plain sent via Intent.EXTRA_TEXT to HTML
try {
- TemporaryStorageProvider shareFileProv = new TemporaryStorageProvider();
- Uri contentUri = TemporaryStorageProvider.createFile(activity,
- KeyFormattingUtils.convertFingerprintToHex(mFingerprint) + Constants.FILE_EXTENSION_ASC);
+ TemporaryFileProvider shareFileProv = new TemporaryFileProvider();
+
+ String filename = KeyFormattingUtils.convertFingerprintToHex(mFingerprint);
+ KeyRing.UserId mainUserId = KeyRing.splitUserId(mUserId);
+ if (mainUserId.name != null) {
+ filename = mainUserId.name;
+ }
+ Uri contentUri = TemporaryFileProvider.createFile(activity, filename + Constants.FILE_EXTENSION_ASC);
BufferedWriter contentWriter = new BufferedWriter(new OutputStreamWriter(
new ParcelFileDescriptor.AutoCloseOutputStream(
@@ -256,18 +231,15 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
sendIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
} catch (FileNotFoundException e) {
- Log.e(Constants.TAG, "error creating temporary Bluetooth key share file!", e);
+ Log.e(Constants.TAG, "Error creating temporary key share file!", e);
// no need for a snackbar because one sharing option doesn't work
// Notify.create(getActivity(), R.string.error_temp_file, Notify.Style.ERROR).show();
}
-
- String title = getString(fingerprintOnly
- ? R.string.title_share_fingerprint_with : R.string.title_share_key);
+ String title = getString(R.string.title_share_key);
Intent shareChooser = Intent.createChooser(sendIntent, title);
startActivity(shareChooser);
-
} catch (PgpGeneralException | IOException e) {
Log.e(Constants.TAG, "error processing key!", e);
Notify.create(activity, R.string.error_key_processing, Notify.Style.ERROR).show();
@@ -277,6 +249,45 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
}
}
+ private void shareFingerprint(boolean toClipboard) {
+ Activity activity = getActivity();
+ if (activity == null || mFingerprint == null) {
+ return;
+ }
+
+ String content;
+ String fingerprint = KeyFormattingUtils.convertFingerprintToHex(mFingerprint);
+ if (!toClipboard) {
+ content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint;
+ } else {
+ content = fingerprint;
+ }
+
+ if (toClipboard) {
+ ClipboardManager clipMan = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
+ if (clipMan == null) {
+ Notify.create(activity, R.string.error_clipboard_copy, Style.ERROR);
+ return;
+ }
+
+ ClipData clip = ClipData.newPlainText(Constants.CLIPBOARD_LABEL, content);
+ clipMan.setPrimaryClip(clip);
+
+ Notify.create(activity, R.string.fingerprint_copied_to_clipboard, Notify.Style.OK).show();
+ return;
+ }
+
+ // let user choose application
+ Intent sendIntent = new Intent(Intent.ACTION_SEND);
+ sendIntent.putExtra(Intent.EXTRA_TEXT, content);
+ sendIntent.setType("text/plain");
+
+ String title = getString(R.string.title_share_fingerprint_with);
+ Intent shareChooser = Intent.createChooser(sendIntent, title);
+
+ startActivity(shareChooser);
+ }
+
private void showQrCodeDialog() {
Intent qrCodeIntent = new Intent(getActivity(), QrCodeViewActivity.class);
@@ -318,11 +329,12 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
mNfcHelper.initNfc(mDataUri);
}
- static final String[] UNIFIED_PROJECTION = new String[] {
- KeyRings._ID, KeyRings.FINGERPRINT
+ static final String[] UNIFIED_PROJECTION = new String[]{
+ KeyRings._ID, KeyRings.FINGERPRINT, KeyRings.USER_ID
};
static final int INDEX_UNIFIED_FINGERPRINT = 1;
+ static final int INDEX_UNIFIED_USER_ID = 2;
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
setContentShown(false);
@@ -351,6 +363,8 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
byte[] fingerprintBlob = data.getBlob(INDEX_UNIFIED_FINGERPRINT);
setFingerprint(fingerprintBlob);
+ mUserId = data.getString(INDEX_UNIFIED_USER_ID);
+
break;
}
}
@@ -367,10 +381,11 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
mFingerprint = null;
}
- /** Load QR Code asynchronously and with a fade in animation */
+ /**
+ * Load QR Code asynchronously and with a fade in animation
+ */
private void setFingerprint(byte[] fingerprintBlob) {
mFingerprint = fingerprintBlob;
- mMasterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(fingerprintBlob);
final String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fingerprintBlob);
mFingerprintView.setText(KeyFormattingUtils.colorizeFingerprint(fingerprint));
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeybaseFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeybaseFragment.java
index 266633061..11c032517 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeybaseFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyKeybaseFragment.java
@@ -40,6 +40,7 @@ import android.widget.TableRow;
import android.widget.TextView;
import com.textuality.keybase.lib.KeybaseException;
+import com.textuality.keybase.lib.KeybaseQuery;
import com.textuality.keybase.lib.Proof;
import com.textuality.keybase.lib.User;
@@ -51,6 +52,7 @@ import org.sufficientlysecure.keychain.service.KeybaseVerificationParcel;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.util.OkHttpKeybaseClient;
import org.sufficientlysecure.keychain.util.ParcelableProxy;
import org.sufficientlysecure.keychain.util.Preferences;
import org.sufficientlysecure.keychain.util.orbot.OrbotHelper;
@@ -224,8 +226,9 @@ public class ViewKeyKeybaseFragment extends LoaderFragment implements
}
}
- // look for evidence from keybase in the background, make tabular version of result
- //
+ /**
+ * look for evidence from keybase in the background, make tabular version of result
+ */
private class DescribeKey extends AsyncTask<String, Void, ResultPage> {
ParcelableProxy mParcelableProxy;
@@ -240,7 +243,9 @@ public class ViewKeyKeybaseFragment extends LoaderFragment implements
final ArrayList<CharSequence> proofList = new ArrayList<CharSequence>();
final Hashtable<Integer, ArrayList<Proof>> proofs = new Hashtable<Integer, ArrayList<Proof>>();
try {
- User keybaseUser = User.findByFingerprint(fingerprint, mParcelableProxy.getProxy());
+ KeybaseQuery keybaseQuery = new KeybaseQuery(new OkHttpKeybaseClient());
+ keybaseQuery.setProxy(mParcelableProxy.getProxy());
+ User keybaseUser = User.findByFingerprint(keybaseQuery, fingerprint);
for (Proof proof : keybaseUser.getProofs()) {
Integer proofType = proof.getType();
appendIfOK(proofs, proofType, proof);
@@ -266,7 +271,12 @@ public class ViewKeyKeybaseFragment extends LoaderFragment implements
} catch (KeybaseException ignored) {
}
- return new ResultPage(getString(R.string.key_trust_results_prefix), proofList);
+ String prefix = "";
+ if (isAdded()) {
+ prefix = getString(R.string.key_trust_results_prefix);
+ }
+
+ return new ResultPage(prefix, proofList);
}
private SpannableStringBuilder formatSpannableString(SpannableStringBuilder proofLinks, String proofType) {
@@ -291,7 +301,10 @@ public class ViewKeyKeybaseFragment extends LoaderFragment implements
if (haveProofFor(proof.getType())) {
ssb.append("\u00a0[");
startAt = ssb.length();
- String verify = getString(R.string.keybase_verify);
+ String verify = "";
+ if (isAdded()) {
+ verify = getString(R.string.keybase_verify);
+ }
ssb.append(verify);
ClickableSpan clicker = new ClickableSpan() {
@Override
@@ -308,6 +321,11 @@ public class ViewKeyKeybaseFragment extends LoaderFragment implements
@Override
protected void onPostExecute(ResultPage result) {
super.onPostExecute(result);
+ // stop if fragment is no longer added to an activity
+ if(!isAdded()) {
+ return;
+ }
+
if (result.mProofs.isEmpty()) {
result.mHeader = getActivity().getString(R.string.key_trust_no_cloud_evidence);
}
@@ -356,7 +374,12 @@ public class ViewKeyKeybaseFragment extends LoaderFragment implements
default:
stringIndex = R.string.keybase_narrative_unknown;
}
- return getActivity().getString(stringIndex);
+
+ if (isAdded()) {
+ return getString(stringIndex);
+ } else {
+ return "";
+ }
}
private void appendIfOK(Hashtable<Integer, ArrayList<Proof>> table, Integer proofType, Proof proof) throws KeybaseException {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java
index 139512ba9..038ebd5dd 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java
@@ -17,6 +17,14 @@
package org.sufficientlysecure.keychain.ui.adapter;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.util.LongSparseArray;
@@ -28,28 +36,26 @@ import org.sufficientlysecure.keychain.operations.results.GetKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing.IteratorWithIOThrow;
+import org.sufficientlysecure.keychain.ui.ImportKeysListFragment.BytesLoaderState;
+import org.sufficientlysecure.keychain.util.FileHelper;
import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.PositionAwareInputStream;
-import java.io.BufferedInputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-
public class ImportKeysListLoader
extends AsyncTaskLoader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> {
final Context mContext;
- final InputData mInputData;
+ final BytesLoaderState mLoaderState;
ArrayList<ImportKeysListEntry> mData = new ArrayList<>();
LongSparseArray<ParcelableKeyRing> mParcelableRings = new LongSparseArray<>();
AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> mEntryListWrapper;
- public ImportKeysListLoader(Context context, InputData inputData) {
+ public ImportKeysListLoader(Context context, BytesLoaderState inputData) {
super(context);
this.mContext = context;
- this.mInputData = inputData;
+ this.mLoaderState = inputData;
}
@Override
@@ -62,12 +68,13 @@ public class ImportKeysListLoader
GetKeyResult getKeyResult = new GetKeyResult(GetKeyResult.RESULT_OK, null);
mEntryListWrapper = new AsyncTaskResultWrapper<>(mData, getKeyResult);
- if (mInputData == null) {
+ if (mLoaderState == null) {
Log.e(Constants.TAG, "Input data is null!");
return mEntryListWrapper;
}
- generateListOfKeyrings(mInputData);
+ InputData inputData = getInputData(getContext(), mLoaderState);
+ generateListOfKeyrings(inputData);
return mEntryListWrapper;
}
@@ -99,12 +106,7 @@ public class ImportKeysListLoader
return mParcelableRings;
}
- /**
- * Reads all PGPKeyRing objects from input
- *
- * @param inputData
- * @return
- */
+ /** Reads all PGPKeyRing objects from the bytes of an InputData object. */
private void generateListOfKeyrings(InputData inputData) {
PositionAwareInputStream progressIn = new PositionAwareInputStream(
inputData.getInputStream());
@@ -132,4 +134,23 @@ public class ImportKeysListLoader
}
}
+ private static InputData getInputData(Context context, BytesLoaderState loaderState) {
+ InputData inputData = null;
+ if (loaderState.mKeyBytes != null) {
+ inputData = new InputData(new ByteArrayInputStream(loaderState.mKeyBytes), loaderState.mKeyBytes.length);
+ } else if (loaderState.mDataUri != null) {
+ try {
+ InputStream inputStream = context.getContentResolver().openInputStream(loaderState.mDataUri);
+ long length = FileHelper.getFileSize(context, loaderState.mDataUri, -1);
+
+ inputData = new InputData(inputStream, length);
+ } catch (FileNotFoundException e) {
+ Log.e(Constants.TAG, "FileNotFoundException!", e);
+ return null;
+ }
+ }
+
+ return inputData;
+ }
+
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySelectableAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySelectableAdapter.java
index 471a20411..7cc37b3a3 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySelectableAdapter.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeySelectableAdapter.java
@@ -1,13 +1,8 @@
package org.sufficientlysecure.keychain.ui.adapter;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
import android.content.Context;
import android.database.Cursor;
-import android.support.v7.internal.widget.AdapterViewCompat;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
@@ -18,6 +13,10 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.util.Log;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
public class KeySelectableAdapter extends KeyAdapter implements OnItemClickListener {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/LinkedIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/LinkedIdsAdapter.java
index 5cf0e6e08..5566c725b 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/LinkedIdsAdapter.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/LinkedIdsAdapter.java
@@ -24,6 +24,7 @@ import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
+import android.os.Build.VERSION_CODES;
import android.support.v4.content.CursorLoader;
import android.util.Log;
import android.view.LayoutInflater;
@@ -228,9 +229,11 @@ public class LinkedIdsAdapter extends UserAttributesAdapter {
}
public void seekAttention() {
- ObjectAnimator anim = SubtleAttentionSeeker.tintText(vComment, 1000);
- anim.setStartDelay(200);
- anim.start();
+ if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
+ ObjectAnimator anim = SubtleAttentionSeeker.tintText(vComment, 1000);
+ anim.setStartDelay(200);
+ anim.start();
+ }
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java
index 972421abe..3e0bc7890 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java
@@ -35,7 +35,6 @@ import android.nfc.TagLostException;
import android.nfc.tech.IsoDep;
import android.os.AsyncTask;
import android.os.Bundle;
-import android.widget.Toast;
import org.spongycastle.bcpg.HashAlgorithmTags;
import org.spongycastle.util.Arrays;
@@ -103,7 +102,7 @@ public abstract class BaseNfcActivity extends BaseActivity {
/**
* Override to handle result of NFC operations (UI thread)
*/
- protected void onNfcPostExecute() throws IOException {
+ protected void onNfcPostExecute() {
final long subKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mNfcFingerprints);
@@ -134,9 +133,16 @@ public abstract class BaseNfcActivity extends BaseActivity {
Notify.create(this, error, Style.WARN).show();
}
+ /**
+ * Override to do something when PIN is wrong, e.g., clear passphrases (UI thread)
+ */
+ protected void onNfcPinError(String error) {
+ onNfcError(error);
+ }
+
public void handleIntentInBackground(final Intent intent) {
// Actual NFC operations are executed in doInBackground to not block the UI thread
- new AsyncTask<Void, Void, Exception>() {
+ new AsyncTask<Void, Void, IOException>() {
@Override
protected void onPreExecute() {
super.onPreExecute();
@@ -144,11 +150,9 @@ public abstract class BaseNfcActivity extends BaseActivity {
}
@Override
- protected Exception doInBackground(Void... params) {
+ protected IOException doInBackground(Void... params) {
try {
handleTagDiscoveredIntent(intent);
- } catch (CardException e) {
- return e;
} catch (IOException e) {
return e;
}
@@ -157,7 +161,7 @@ public abstract class BaseNfcActivity extends BaseActivity {
}
@Override
- protected void onPostExecute(Exception exception) {
+ protected void onPostExecute(IOException exception) {
super.onPostExecute(exception);
if (exception != null) {
@@ -165,11 +169,7 @@ public abstract class BaseNfcActivity extends BaseActivity {
return;
}
- try {
- onNfcPostExecute();
- } catch (IOException e) {
- handleNfcError(e);
- }
+ onNfcPostExecute();
}
}.execute();
}
@@ -221,24 +221,30 @@ public abstract class BaseNfcActivity extends BaseActivity {
}
}
- private void handleNfcError(Exception e) {
- Log.e(Constants.TAG, "nfc error", e);
+ private void handleNfcError(IOException e) {
if (e instanceof TagLostException) {
onNfcError(getString(R.string.error_nfc_tag_lost));
return;
}
+ if (e instanceof IsoDepNotSupportedException) {
+ onNfcError(getString(R.string.error_nfc_iso_dep_not_supported));
+ return;
+ }
+
short status;
if (e instanceof CardException) {
status = ((CardException) e).getResponseCode();
} else {
status = -1;
}
- // When entering a PIN, a status of 63CX indicates X attempts remaining.
- if ((status & (short)0xFFF0) == 0x63C0) {
+
+ // Wrong PIN, a status of 63CX indicates X attempts remaining.
+ if ((status & (short) 0xFFF0) == 0x63C0) {
int tries = status & 0x000F;
- onNfcError(getResources().getQuantityString(R.plurals.error_pin, tries, tries));
+ // hook to do something different when PIN is wrong
+ onNfcPinError(getResources().getQuantityString(R.plurals.error_pin, tries, tries));
return;
}
@@ -272,7 +278,7 @@ public abstract class BaseNfcActivity extends BaseActivity {
break;
}
case 0x6700: {
- onNfcError(getString(R.string.error_nfc_wrong_length));
+ onNfcPinError(getString(R.string.error_nfc_wrong_length));
break;
}
case 0x6982: {
@@ -307,12 +313,6 @@ public abstract class BaseNfcActivity extends BaseActivity {
}
- public void handlePinError() {
- toast("Wrong PIN!");
- setResult(RESULT_CANCELED);
- finish();
- }
-
/**
* Called when the system is about to start resuming a previous activity,
* disables NFC Foreground Dispatch
@@ -337,18 +337,11 @@ public abstract class BaseNfcActivity extends BaseActivity {
protected void obtainYubiKeyPin(RequiredInputParcel requiredInput) {
- // shortcut if we only use the default yubikey pin
- Preferences prefs = Preferences.getPreferences(this);
- if (prefs.useDefaultYubiKeyPin()) {
- mPin = new Passphrase("123456");
- return;
- }
-
try {
- Passphrase phrase = PassphraseCacheService.getCachedPassphrase(this,
+ Passphrase passphrase = PassphraseCacheService.getCachedPassphrase(this,
requiredInput.getMasterKeyId(), requiredInput.getSubKeyId());
- if (phrase != null) {
- mPin = phrase;
+ if (passphrase != null) {
+ mPin = passphrase;
return;
}
@@ -363,10 +356,6 @@ public abstract class BaseNfcActivity extends BaseActivity {
}
- protected void setYubiKeyPin(Passphrase pin) {
- mPin = pin;
- }
-
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
@@ -406,6 +395,9 @@ public abstract class BaseNfcActivity extends BaseActivity {
// Connect to the detected tag, setting a couple of settings
mIsoDep = IsoDep.get(detectedTag);
+ if (mIsoDep == null) {
+ throw new IsoDepNotSupportedException("Tag does not support ISO-DEP (ISO 14443-4)");
+ }
mIsoDep.setTimeout(TIMEOUT); // timeout is set to 100 seconds to avoid cancellation during calculation
mIsoDep.connect();
@@ -448,7 +440,7 @@ public abstract class BaseNfcActivity extends BaseActivity {
* @return The long key id of the requested key, or null if not found.
*/
public Long nfcGetKeyId(int idx) throws IOException {
- byte[] fp = nfcGetFingerprint(idx);
+ byte[] fp = nfcGetMasterKeyFingerprint(idx);
if (fp == null) {
return null;
}
@@ -469,7 +461,7 @@ public abstract class BaseNfcActivity extends BaseActivity {
byte[] buf = mIsoDep.transceive(Hex.decode(data));
Iso7816TLV tlv = Iso7816TLV.readSingle(buf, true);
- Log.d(Constants.TAG, "nfc tlv data:\n" + tlv.prettyPrint());
+ Log.d(Constants.TAG, "nfcGetFingerprints() Iso7816TLV tlv data:\n" + tlv.prettyPrint());
Iso7816TLV fptlv = Iso7816TLV.findRecursive(tlv, 0xc5);
if (fptlv == null) {
@@ -494,8 +486,11 @@ public abstract class BaseNfcActivity 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 byte[] nfcGetFingerprint(int idx) throws IOException {
+ public byte[] nfcGetMasterKeyFingerprint(int idx) throws IOException {
byte[] data = nfcGetFingerprints();
+ if (data == null) {
+ return null;
+ }
// return the master key fingerprint
ByteBuffer fpbuf = ByteBuffer.wrap(data);
@@ -507,14 +502,11 @@ public abstract class BaseNfcActivity extends BaseActivity {
}
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));
}
@@ -648,8 +640,6 @@ public abstract class BaseNfcActivity extends BaseActivity {
String decryptedSessionKey = nfcGetDataField(second);
- Log.d(Constants.TAG, "decryptedSessionKey: " + decryptedSessionKey);
-
return Hex.decode(decryptedSessionKey);
}
@@ -671,18 +661,8 @@ public abstract class BaseNfcActivity extends BaseActivity {
// 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);
- String response = nfcCommunicate(login); // login
+ String response = tryPin(mode, pin); // login
if (!response.equals(accepted)) {
- handlePinError();
throw new CardException("Bad PIN!", parseCardStatus(response));
}
@@ -696,6 +676,51 @@ public abstract class BaseNfcActivity extends BaseActivity {
}
}
+ public void nfcResetCard() throws IOException {
+ String accepted = "9000";
+
+ // try wrong PIN 4 times until counter goes to C0
+ byte[] pin = "XXXXXX".getBytes();
+ for (int i = 0; i <= 4; i++) {
+ String response = tryPin(0x81, pin);
+ if (response.equals(accepted)) { // Should NOT accept!
+ throw new CardException("Should never happen, XXXXXX has been accepted!", parseCardStatus(response));
+ }
+ }
+
+ // try wrong Admin PIN 4 times until counter goes to C0
+ byte[] adminPin = "XXXXXXXX".getBytes();
+ for (int i = 0; i <= 4; i++) {
+ String response = tryPin(0x83, adminPin);
+ if (response.equals(accepted)) { // Should NOT accept!
+ throw new CardException("Should never happen, XXXXXXXX has been accepted", parseCardStatus(response));
+ }
+ }
+
+ // reactivate card!
+ String reactivate1 = "00" + "e6" + "00" + "00";
+ String reactivate2 = "00" + "44" + "00" + "00";
+ String response1 = nfcCommunicate(reactivate1);
+ String response2 = nfcCommunicate(reactivate2);
+ if (!response1.equals(accepted) || !response2.equals(accepted)) {
+ throw new CardException("Reactivating failed!", parseCardStatus(response1));
+ }
+
+ }
+
+ private String tryPin(int mode, byte[] pin) throws IOException {
+ // 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);
+
+ return nfcCommunicate(login);
+ }
+
/** Modifies the user's PW1 or PW3. Before sending, the new PIN will be validated for
* conformance to the card's requirements for key length.
*
@@ -737,7 +762,6 @@ public abstract class BaseNfcActivity extends BaseActivity {
+ getHex(newPin);
String response = nfcCommunicate(changeReferenceDataApdu); // change PIN
if (!response.equals("9000")) {
- handlePinError();
throw new CardException("Failed to change PIN", parseCardStatus(response));
}
}
@@ -906,15 +930,6 @@ public abstract class BaseNfcActivity extends BaseActivity {
}
/**
- * Prints a message to the screen
- *
- * @param text the text which should be contained within the toast
- */
- protected 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!
*/
@@ -930,12 +945,10 @@ public abstract class BaseNfcActivity extends BaseActivity {
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.i(Constants.TAG, "NfcForegroundDispatch Exception: Activity is not currently in the foreground?", e);
}
Log.d(Constants.TAG, "NfcForegroundDispatch has been enabled!");
}
@@ -952,14 +965,23 @@ public abstract class BaseNfcActivity extends BaseActivity {
}
public String nfcGetHolderName(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);
+ try {
+ 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;
+ } catch (IndexOutOfBoundsException e) {
+ // try-catch for https://github.com/FluffyKaon/OpenPGP-Card
+ // Note: This should not happen, but happens with
+ // https://github.com/FluffyKaon/OpenPGP-Card, thus return an empty string for now!
+
+ Log.e(Constants.TAG, "Couldn't get holder name, returning empty string!", e);
+ return "";
+ }
}
private String nfcGetDataField(String output) {
@@ -974,6 +996,14 @@ public abstract class BaseNfcActivity extends BaseActivity {
return new String(Hex.encode(raw));
}
+ public class IsoDepNotSupportedException extends IOException {
+
+ public IsoDepNotSupportedException(String detailMessage) {
+ super(detailMessage);
+ }
+
+ }
+
public class CardException extends IOException {
private short mResponseCode;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java
index 52c6797d5..7ab9c7237 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java
@@ -84,6 +84,7 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
public static final int REQUEST_CODE_RETRY_UPLOAD = 4;
private Integer mProgressMessageResource;
+ private boolean mCancellable = false;
private FragmentActivity mActivity;
private Fragment mFragment;
@@ -118,6 +119,10 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
mProgressMessageResource = id;
}
+ public void setProgressCancellable(boolean cancellable) {
+ mCancellable = cancellable;
+ }
+
private void initiateInputActivity(RequiredInputParcel requiredInput,
CryptoInputParcel cryptoInputParcel) {
@@ -136,7 +141,8 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
}
case PASSPHRASE:
- case PASSPHRASE_SYMMETRIC: {
+ case PASSPHRASE_SYMMETRIC:
+ case BACKUP_CODE: {
Intent intent = new Intent(activity, PassphraseDialogActivity.class);
intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT, requiredInput);
intent.putExtra(PassphraseDialogActivity.EXTRA_CRYPTO_INPUT, cryptoInputParcel);
@@ -310,7 +316,7 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
if (mProgressMessageResource != null) {
saveHandler.showProgressDialog(
activity.getString(mProgressMessageResource),
- ProgressDialog.STYLE_HORIZONTAL, false);
+ ProgressDialog.STYLE_HORIZONTAL, mCancellable);
}
activity.startService(intent);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java
index 47bc7dfda..3d96f3c6d 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java
@@ -24,6 +24,7 @@ import java.net.URI;
import java.net.URISyntaxException;
import android.app.Activity;
+import android.support.design.widget.TextInputLayout;
import android.support.v7.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
@@ -44,6 +45,7 @@ import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.CheckBox;
+import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
@@ -54,6 +56,7 @@ import com.squareup.okhttp.Request;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
+import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Preferences;
import org.sufficientlysecure.keychain.util.TlsHelper;
@@ -68,11 +71,9 @@ public class AddEditKeyserverDialogFragment extends DialogFragment implements On
private static final String ARG_KEYSERVER = "arg_keyserver";
public static final int MESSAGE_OKAY = 1;
- public static final int MESSAGE_VERIFICATION_FAILED = 2;
public static final String MESSAGE_KEYSERVER = "new_keyserver";
public static final String MESSAGE_VERIFIED = "verified";
- public static final String MESSAGE_FAILURE_REASON = "failure_reason";
public static final String MESSAGE_KEYSERVER_DELETED = "keyserver_deleted";
public static final String MESSAGE_DIALOG_ACTION = "message_dialog_action";
public static final String MESSAGE_EDIT_POSITION = "keyserver_edited_position";
@@ -82,7 +83,9 @@ public class AddEditKeyserverDialogFragment extends DialogFragment implements On
private int mPosition;
private EditText mKeyserverEditText;
+ private TextInputLayout mKeyserverEditTextLayout;
private CheckBox mVerifyKeyserverCheckBox;
+ private CheckBox mOnlyTrustedKeyserverCheckBox;
public enum DialogAction {
ADD,
@@ -91,7 +94,8 @@ public class AddEditKeyserverDialogFragment extends DialogFragment implements On
public enum FailureReason {
INVALID_URL,
- CONNECTION_FAILED
+ CONNECTION_FAILED,
+ NO_PINNED_CERTIFICATE
}
public static AddEditKeyserverDialogFragment newInstance(Messenger messenger,
@@ -126,7 +130,15 @@ public class AddEditKeyserverDialogFragment extends DialogFragment implements On
alert.setView(view);
mKeyserverEditText = (EditText) view.findViewById(R.id.keyserver_url_edit_text);
- mVerifyKeyserverCheckBox = (CheckBox) view.findViewById(R.id.verify_keyserver_checkbox);
+ mKeyserverEditTextLayout = (TextInputLayout) view.findViewById(R.id.keyserver_url_edit_text_layout);
+ mVerifyKeyserverCheckBox = (CheckBox) view.findViewById(R.id.verify_connection_checkbox);
+ mOnlyTrustedKeyserverCheckBox = (CheckBox) view.findViewById(R.id.only_trusted_keyserver_checkbox);
+ mVerifyKeyserverCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ mOnlyTrustedKeyserverCheckBox.setEnabled(isChecked);
+ }
+ });
switch (mDialogAction) {
case ADD: {
@@ -212,6 +224,8 @@ public class AddEditKeyserverDialogFragment extends DialogFragment implements On
positiveButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
+ mKeyserverEditTextLayout.setErrorEnabled(false);
+
// behaviour same for edit and add
final String keyserverUrl = mKeyserverEditText.getText().toString();
if (mVerifyKeyserverCheckBox.isChecked()) {
@@ -220,13 +234,20 @@ public class AddEditKeyserverDialogFragment extends DialogFragment implements On
OrbotHelper.DialogActions dialogActions = new OrbotHelper.DialogActions() {
@Override
public void onOrbotStarted() {
- verifyConnection(keyserverUrl,
- proxyPrefs.parcelableProxy.getProxy());
+ verifyConnection(
+ keyserverUrl,
+ proxyPrefs.parcelableProxy.getProxy(),
+ mOnlyTrustedKeyserverCheckBox.isChecked()
+ );
}
@Override
public void onNeutralButton() {
- verifyConnection(keyserverUrl, null);
+ verifyConnection(
+ keyserverUrl,
+ null,
+ mOnlyTrustedKeyserverCheckBox.isChecked()
+ );
}
@Override
@@ -236,7 +257,11 @@ public class AddEditKeyserverDialogFragment extends DialogFragment implements On
};
if (OrbotHelper.putOrbotInRequiredState(dialogActions, getActivity())) {
- verifyConnection(keyserverUrl, proxyPrefs.parcelableProxy.getProxy());
+ verifyConnection(
+ keyserverUrl,
+ proxyPrefs.parcelableProxy.getProxy(),
+ mOnlyTrustedKeyserverCheckBox.isChecked()
+ );
}
} else {
dismiss();
@@ -272,14 +297,28 @@ public class AddEditKeyserverDialogFragment extends DialogFragment implements On
sendMessageToHandler(MESSAGE_OKAY, data);
}
- public void verificationFailed(FailureReason reason) {
- Bundle data = new Bundle();
- data.putSerializable(MESSAGE_FAILURE_REASON, reason);
+ public void verificationFailed(FailureReason failureReason) {
+ switch (failureReason) {
+ case CONNECTION_FAILED: {
+ mKeyserverEditTextLayout.setError(
+ getString(R.string.add_keyserver_connection_failed));
+ break;
+ }
+ case INVALID_URL: {
+ mKeyserverEditTextLayout.setError(
+ getString(R.string.add_keyserver_invalid_url));
+ break;
+ }
+ case NO_PINNED_CERTIFICATE: {
+ mKeyserverEditTextLayout.setError(
+ getString(R.string.add_keyserver_keyserver_not_trusted));
+ break;
+ }
+ }
- sendMessageToHandler(MESSAGE_VERIFICATION_FAILED, data);
}
- public void verifyConnection(String keyserver, final Proxy proxy) {
+ public void verifyConnection(String keyserver, final Proxy proxy, final boolean onlyTrustedKeyserver) {
new AsyncTask<String, Void, FailureReason>() {
ProgressDialog mProgressDialog;
@@ -288,7 +327,7 @@ public class AddEditKeyserverDialogFragment extends DialogFragment implements On
@Override
protected void onPreExecute() {
mProgressDialog = new ProgressDialog(getActivity());
- mProgressDialog.setMessage(getString(R.string.progress_verifying_keyserver_url));
+ mProgressDialog.setMessage(getString(R.string.progress_verifying_keyserver_connection));
mProgressDialog.setCancelable(false);
mProgressDialog.show();
}
@@ -316,7 +355,18 @@ public class AddEditKeyserverDialogFragment extends DialogFragment implements On
Log.d("Converted URL", newKeyserver.toString());
OkHttpClient client = HkpKeyserver.getClient(newKeyserver.toURL(), proxy);
- TlsHelper.pinCertificateIfNecessary(client, newKeyserver.toURL());
+
+ // don't follow any redirects
+ client.setFollowRedirects(false);
+ client.setFollowSslRedirects(false);
+
+ if (onlyTrustedKeyserver
+ && !TlsHelper.usePinnedCertificateIfAvailable(client, newKeyserver.toURL())) {
+ Log.w(Constants.TAG, "No pinned certificate for this host in OpenKeychain's assets.");
+ reason = FailureReason.NO_PINNED_CERTIFICATE;
+ return reason;
+ }
+
client.newCall(new Request.Builder().url(newKeyserver.toURL()).build()).execute();
} catch (TlsHelper.TlsHelperException e) {
reason = FailureReason.CONNECTION_FAILED;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java
index b51d081e1..cd5281c7c 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java
@@ -348,30 +348,30 @@ public class AddSubkeyDialogFragment extends DialogFragment {
/**
* <h3>RSA</h3>
- * <p>for RSA algorithm, key length must be greater than 1024 (according to
- * <a href="https://github.com/open-keychain/open-keychain/issues/102">#102</a>). Possibility to generate keys bigger
+ * <p>for RSA algorithm, key length must be greater than 2048. Possibility to generate keys bigger
* than 8192 bits is currently disabled, because it's almost impossible to generate them on a mobile device (check
* <a href="http://www.javamex.com/tutorials/cryptography/rsa_key_length.shtml">RSA key length plot</a> and
* <a href="http://www.keylength.com/">Cryptographic Key Length Recommendation</a>). Also, key length must be a
* multiplicity of 8.</p>
* <h3>ElGamal</h3>
- * <p>For ElGamal algorithm, supported key lengths are 1536, 2048, 3072, 4096 or 8192 bits.</p>
+ * <p>For ElGamal algorithm, supported key lengths are 2048, 3072, 4096 or 8192 bits.</p>
* <h3>DSA</h3>
- * <p>For DSA algorithm key length must be between 512 and 1024. Also, it must me dividable by 64.</p>
+ * <p>For DSA algorithm key length must be between 2048 and 3072. Also, it must me dividable by 64.</p>
*
* @return correct key length, according to SpongyCastle specification. Returns <code>-1</code>, if key length is
* inappropriate.
*/
private int getProperKeyLength(Algorithm algorithm, int currentKeyLength) {
- final int[] elGamalSupportedLengths = {1536, 2048, 3072, 4096, 8192};
+ final int[] elGamalSupportedLengths = {2048, 3072, 4096, 8192};
int properKeyLength = -1;
switch (algorithm) {
- case RSA:
- if (currentKeyLength > 1024 && currentKeyLength <= 16384) {
+ case RSA: {
+ if (currentKeyLength >= 2048 && currentKeyLength <= 16384) {
properKeyLength = currentKeyLength + ((8 - (currentKeyLength % 8)) % 8);
}
break;
- case ELGAMAL:
+ }
+ case ELGAMAL: {
int[] elGammalKeyDiff = new int[elGamalSupportedLengths.length];
for (int i = 0; i < elGamalSupportedLengths.length; i++) {
elGammalKeyDiff[i] = Math.abs(elGamalSupportedLengths[i] - currentKeyLength);
@@ -386,11 +386,14 @@ public class AddSubkeyDialogFragment extends DialogFragment {
}
properKeyLength = elGamalSupportedLengths[minimalIndex];
break;
- case DSA:
- if (currentKeyLength >= 512 && currentKeyLength <= 1024) {
+ }
+ case DSA: {
+ // Bouncy Castle supports 4096 maximum
+ if (currentKeyLength >= 2048 && currentKeyLength <= 4096) {
properKeyLength = currentKeyLength + ((64 - (currentKeyLength % 64)) % 64);
}
break;
+ }
}
return properKeyLength;
}
@@ -424,7 +427,7 @@ public class AddSubkeyDialogFragment extends DialogFragment {
final ArrayAdapter<CharSequence> keySizeAdapter = (ArrayAdapter<CharSequence>) mKeySizeSpinner.getAdapter();
keySizeAdapter.clear();
switch (algorithm) {
- case RSA:
+ case RSA: {
replaceArrayAdapterContent(keySizeAdapter, R.array.rsa_key_size_spinner_values);
mKeySizeSpinner.setSelection(1);
mKeySizeRow.setVisibility(View.VISIBLE);
@@ -450,7 +453,8 @@ public class AddSubkeyDialogFragment extends DialogFragment {
}
mFlagAuthenticate.setChecked(false);
break;
- case ELGAMAL:
+ }
+ case ELGAMAL: {
replaceArrayAdapterContent(keySizeAdapter, R.array.elgamal_key_size_spinner_values);
mKeySizeSpinner.setSelection(3);
mKeySizeRow.setVisibility(View.VISIBLE);
@@ -466,7 +470,8 @@ public class AddSubkeyDialogFragment extends DialogFragment {
mFlagAuthenticate.setChecked(false);
mFlagAuthenticate.setEnabled(false);
break;
- case DSA:
+ }
+ case DSA: {
replaceArrayAdapterContent(keySizeAdapter, R.array.dsa_key_size_spinner_values);
mKeySizeSpinner.setSelection(2);
mKeySizeRow.setVisibility(View.VISIBLE);
@@ -482,7 +487,8 @@ public class AddSubkeyDialogFragment extends DialogFragment {
mFlagAuthenticate.setChecked(false);
mFlagAuthenticate.setEnabled(false);
break;
- case ECDSA:
+ }
+ case ECDSA: {
mKeySizeRow.setVisibility(View.GONE);
mCurveRow.setVisibility(View.VISIBLE);
mCustomKeyInfoTextView.setText("");
@@ -496,7 +502,8 @@ public class AddSubkeyDialogFragment extends DialogFragment {
mFlagAuthenticate.setEnabled(true);
mFlagAuthenticate.setChecked(false);
break;
- case ECDH:
+ }
+ case ECDH: {
mKeySizeRow.setVisibility(View.GONE);
mCurveRow.setVisibility(View.VISIBLE);
mCustomKeyInfoTextView.setText("");
@@ -510,6 +517,7 @@ public class AddSubkeyDialogFragment extends DialogFragment {
mFlagAuthenticate.setChecked(false);
mFlagAuthenticate.setEnabled(false);
break;
+ }
}
keySizeAdapter.notifyDataSetChanged();
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java
deleted file mode 100644
index 84774ae5e..000000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * 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.ui.dialog;
-
-import android.app.Activity;
-import android.app.Dialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.RemoteException;
-import android.support.v4.app.DialogFragment;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.CheckBox;
-import android.widget.EditText;
-import android.widget.ImageButton;
-import android.widget.TextView;
-
-import org.sufficientlysecure.keychain.Constants;
-import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.ui.util.Notify;
-import org.sufficientlysecure.keychain.util.FileHelper;
-import org.sufficientlysecure.keychain.util.Log;
-
-import java.io.File;
-
-/**
- * This is a file chooser dialog no longer used with KitKat
- */
-public class FileDialogFragment extends DialogFragment {
- private static final String ARG_MESSENGER = "messenger";
- private static final String ARG_TITLE = "title";
- private static final String ARG_MESSAGE = "message";
- private static final String ARG_DEFAULT_FILE = "default_file";
- private static final String ARG_CHECKBOX_TEXT = "checkbox_text";
-
- public static final int MESSAGE_OKAY = 1;
-
- public static final String MESSAGE_DATA_FILE = "file";
- public static final String MESSAGE_DATA_CHECKED = "checked";
-
- private Messenger mMessenger;
-
- private EditText mFilename;
- private ImageButton mBrowse;
- private CheckBox mCheckBox;
- private TextView mMessageTextView;
-
- private File mFile;
-
- private static final int REQUEST_CODE = 0x00007004;
-
- /**
- * Creates new instance of this file dialog fragment
- */
- public static FileDialogFragment newInstance(Messenger messenger, String title, String message,
- File defaultFile, String checkboxText) {
- FileDialogFragment frag = new FileDialogFragment();
- Bundle args = new Bundle();
- args.putParcelable(ARG_MESSENGER, messenger);
-
- args.putString(ARG_TITLE, title);
- args.putString(ARG_MESSAGE, message);
- args.putString(ARG_DEFAULT_FILE, defaultFile.getAbsolutePath());
- args.putString(ARG_CHECKBOX_TEXT, checkboxText);
-
- frag.setArguments(args);
-
- return frag;
- }
-
- /**
- * Creates dialog
- */
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- final Activity activity = getActivity();
-
- mMessenger = getArguments().getParcelable(ARG_MESSENGER);
-
- String title = getArguments().getString(ARG_TITLE);
- String message = getArguments().getString(ARG_MESSAGE);
- mFile = new File(getArguments().getString(ARG_DEFAULT_FILE));
- if (!mFile.isAbsolute()) {
- // We use OK dir by default
- mFile = new File(Constants.Path.APP_DIR.getAbsolutePath(), mFile.getName());
- }
- String checkboxText = getArguments().getString(ARG_CHECKBOX_TEXT);
-
- LayoutInflater inflater = (LayoutInflater) activity
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity);
- alert.setTitle(title);
-
- View view = inflater.inflate(R.layout.file_dialog, null);
-
- mMessageTextView = (TextView) view.findViewById(R.id.message);
- mMessageTextView.setText(message);
-
- mFilename = (EditText) view.findViewById(R.id.input);
- mFilename.setText(mFile.getName());
- mBrowse = (ImageButton) view.findViewById(R.id.btn_browse);
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
- mBrowse.setVisibility(View.GONE);
- } else {
- mBrowse.setOnClickListener(new View.OnClickListener() {
- public void onClick(View v) {
- // only .asc or .gpg files
- // setting it to text/plain prevents Cynaogenmod's file manager from selecting asc
- // or gpg types!
- FileHelper.saveDocumentKitKat(
- FileDialogFragment.this, "*/*", mFile.getName(), REQUEST_CODE);
- }
- });
- }
-
- mCheckBox = (CheckBox) view.findViewById(R.id.checkbox);
- if (checkboxText == null) {
- mCheckBox.setEnabled(false);
- mCheckBox.setVisibility(View.GONE);
- } else {
- mCheckBox.setEnabled(true);
- mCheckBox.setVisibility(View.VISIBLE);
- mCheckBox.setText(checkboxText);
- mCheckBox.setChecked(true);
- }
-
- alert.setView(view);
-
- alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
-
- @Override
- public void onClick(DialogInterface dialog, int id) {
- dismiss();
-
- String currentFilename = mFilename.getText().toString();
- if (currentFilename == null || currentFilename.isEmpty()) {
- // No file is like pressing cancel, UI: maybe disable positive button in this case?
- return;
- }
-
- if (mFile == null || currentFilename.startsWith("/")) {
- mFile = new File(currentFilename);
- } else if (!mFile.getName().equals(currentFilename)) {
- // We update our File object if user changed name!
- mFile = new File(mFile.getParentFile(), currentFilename);
- }
-
- boolean checked = mCheckBox.isEnabled() && mCheckBox.isChecked();
-
- // return resulting data back to activity
- Bundle data = new Bundle();
- data.putString(MESSAGE_DATA_FILE, mFile.getAbsolutePath());
- data.putBoolean(MESSAGE_DATA_CHECKED, checked);
-
- sendMessageToHandler(MESSAGE_OKAY, data);
- }
- });
-
- alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
-
- @Override
- public void onClick(DialogInterface dialog, int id) {
- dismiss();
- }
- });
- return alert.show();
- }
-
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- switch (requestCode & 0xFFFF) {
- case REQUEST_CODE: {
- if (resultCode == Activity.RESULT_OK && data != null) {
- File file = new File(data.getData().getPath());
- if (file.getParentFile().exists()) {
- mFile = file;
- mFilename.setText(mFile.getName());
- } else {
- Notify.create(getActivity(), R.string.no_file_selected, Notify.Style.ERROR).show();
- }
- }
-
- break;
- }
-
- default:
- super.onActivityResult(requestCode, resultCode, data);
-
- break;
- }
- }
-
- /**
- * Send message back to handler which is initialized in a activity
- *
- * @param what Message integer you want to send
- */
- private void sendMessageToHandler(Integer what, Bundle data) {
- Message msg = Message.obtain();
- msg.what = what;
- if (data != null) {
- msg.setData(data);
- }
-
- try {
- mMessenger.send(msg);
- } catch (RemoteException e) {
- Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
- } catch (NullPointerException e) {
- Log.w(Constants.TAG, "Messenger is null!", e);
- }
- }
-}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareLogDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareLogDialogFragment.java
new file mode 100644
index 000000000..d4783fad2
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ShareLogDialogFragment.java
@@ -0,0 +1,79 @@
+/*
+ * 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.dialog;
+
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.DialogFragment;
+import android.view.ContextThemeWrapper;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
+
+public class ShareLogDialogFragment extends DialogFragment {
+ private static final String ARG_STREAM = "stream";
+
+ public static ShareLogDialogFragment newInstance(Uri stream) {
+ Bundle args = new Bundle();
+ args.putParcelable(ARG_STREAM, stream);
+
+ ShareLogDialogFragment fragment = new ShareLogDialogFragment();
+ fragment.setArguments(args);
+
+ return fragment;
+ }
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+
+ final Uri stream = getArguments().getParcelable(ARG_STREAM);
+
+ ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(getActivity());
+
+ CustomAlertDialogBuilder builder = new CustomAlertDialogBuilder(theme);
+ builder.setTitle(R.string.share_log_dialog_title)
+ .setMessage(R.string.share_log_dialog_message)
+ .setNegativeButton(R.string.share_log_dialog_cancel_button,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dismiss();
+ }
+ })
+ .setPositiveButton(R.string.share_log_dialog_share_button,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dismiss();
+ Intent intent = new Intent(Intent.ACTION_SEND);
+ intent.putExtra(Intent.EXTRA_STREAM, stream);
+ intent.setType("text/plain");
+ intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ startActivity(intent);
+ }
+ });
+
+ return builder.show();
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep2Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep2Fragment.java
index 22a201ba3..a320ea3b2 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep2Fragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep2Fragment.java
@@ -27,7 +27,6 @@ import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.EditText;
-import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.linked.resources.GenericHttpsResource;
@@ -35,7 +34,6 @@ import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.util.FileHelper;
-import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.net.URI;
@@ -134,10 +132,8 @@ public class LinkedIdCreateHttpsStep2Fragment extends LinkedIdCreateFinalFragmen
String targetName = "pgpkey.txt";
- FileHelper.saveDocument(this,
- targetName, Uri.fromFile(new File(Constants.Path.APP_DIR, targetName)),
- "text/plain", R.string.title_decrypt_to_file, R.string.specify_file_to_decrypt_to,
- REQUEST_CODE_OUTPUT);
+ // TODO: not supported on Android < 4.4
+ FileHelper.saveDocument(this, targetName, "text/plain", REQUEST_CODE_OUTPUT);
}
private void saveFile(Uri uri) {
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 8f5753dae..b9b837d71 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
@@ -28,6 +28,7 @@ import android.text.style.ForegroundColorSpan;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
+import android.widget.ViewAnimator;
import org.openintents.openpgp.OpenPgpDecryptionResult;
import org.openintents.openpgp.OpenPgpSignatureResult;
@@ -440,14 +441,15 @@ public class KeyFormattingUtils {
View getSignatureLayout();
TextView getSignatureUserName();
TextView getSignatureUserEmail();
- TextView getSignatureAction();
+ ViewAnimator getSignatureAction();
boolean hasEncrypt();
}
@SuppressWarnings("deprecation") // context.getDrawable is api lvl 21, need to use deprecated
- public static void setStatus(Resources resources, StatusHolder holder, DecryptVerifyResult result) {
+ public static void setStatus(Resources resources, StatusHolder holder, DecryptVerifyResult result,
+ boolean processingkeyLookup) {
if (holder.hasEncrypt()) {
OpenPgpDecryptionResult decryptionResult = result.getDecryptionResult();
@@ -488,7 +490,7 @@ public class KeyFormattingUtils {
OpenPgpSignatureResult signatureResult = result.getSignatureResult();
int sigText, sigIcon, sigColor;
- int sigActionText, sigActionIcon;
+ int sigActionDisplayedChild;
switch (signatureResult.getResult()) {
@@ -500,8 +502,7 @@ public class KeyFormattingUtils {
sigColor = R.color.key_flag_gray;
// won't be used, but makes compiler happy
- sigActionText = 0;
- sigActionIcon = 0;
+ sigActionDisplayedChild = -1;
break;
}
@@ -510,8 +511,7 @@ public class KeyFormattingUtils {
sigIcon = R.drawable.status_signature_verified_cutout_24dp;
sigColor = R.color.key_flag_green;
- sigActionText = R.string.decrypt_result_action_show;
- sigActionIcon = R.drawable.ic_vpn_key_grey_24dp;
+ sigActionDisplayedChild = 0;
break;
}
@@ -520,8 +520,7 @@ public class KeyFormattingUtils {
sigIcon = R.drawable.status_signature_unverified_cutout_24dp;
sigColor = R.color.key_flag_orange;
- sigActionText = R.string.decrypt_result_action_show;
- sigActionIcon = R.drawable.ic_vpn_key_grey_24dp;
+ sigActionDisplayedChild = 0;
break;
}
@@ -530,8 +529,7 @@ public class KeyFormattingUtils {
sigIcon = R.drawable.status_signature_revoked_cutout_24dp;
sigColor = R.color.key_flag_red;
- sigActionText = R.string.decrypt_result_action_show;
- sigActionIcon = R.drawable.ic_vpn_key_grey_24dp;
+ sigActionDisplayedChild = 0;
break;
}
@@ -540,8 +538,7 @@ public class KeyFormattingUtils {
sigIcon = R.drawable.status_signature_expired_cutout_24dp;
sigColor = R.color.key_flag_red;
- sigActionText = R.string.decrypt_result_action_show;
- sigActionIcon = R.drawable.ic_vpn_key_grey_24dp;
+ sigActionDisplayedChild = 0;
break;
}
@@ -550,8 +547,7 @@ public class KeyFormattingUtils {
sigIcon = R.drawable.status_signature_unknown_cutout_24dp;
sigColor = R.color.key_flag_red;
- sigActionText = R.string.decrypt_result_action_Lookup;
- sigActionIcon = R.drawable.ic_file_download_grey_24dp;
+ sigActionDisplayedChild = 1;
break;
}
@@ -560,8 +556,7 @@ public class KeyFormattingUtils {
sigIcon = R.drawable.status_signature_invalid_cutout_24dp;
sigColor = R.color.key_flag_red;
- sigActionText = R.string.decrypt_result_action_show;
- sigActionIcon = R.drawable.ic_vpn_key_grey_24dp;
+ sigActionDisplayedChild = 0;
break;
}
@@ -571,27 +566,31 @@ public class KeyFormattingUtils {
sigIcon = R.drawable.status_signature_invalid_cutout_24dp;
sigColor = R.color.key_flag_red;
- sigActionText = R.string.decrypt_result_action_show;
- sigActionIcon = R.drawable.ic_vpn_key_grey_24dp;
+ // won't be used, but makes compiler happy
+ sigActionDisplayedChild = -1;
break;
}
}
+ // possibly switch out "Lookup" button for progress bar
+ if (sigActionDisplayedChild == 1 && processingkeyLookup) {
+ sigActionDisplayedChild = 2;
+ }
+
int sigColorRes = resources.getColor(sigColor);
holder.getSignatureStatusIcon().setColorFilter(sigColorRes, PorterDuff.Mode.SRC_IN);
holder.getSignatureStatusIcon().setImageDrawable(resources.getDrawable(sigIcon));
holder.getSignatureStatusText().setText(sigText);
holder.getSignatureStatusText().setTextColor(sigColorRes);
- if (signatureResult.getResult() != OpenPgpSignatureResult.RESULT_NO_SIGNATURE) {
+ if (signatureResult.getResult() != OpenPgpSignatureResult.RESULT_NO_SIGNATURE
+ && signatureResult.getResult() != OpenPgpSignatureResult.RESULT_INVALID_SIGNATURE) {
// has a signature, thus display layouts
holder.getSignatureLayout().setVisibility(View.VISIBLE);
- holder.getSignatureAction().setText(sigActionText);
- holder.getSignatureAction().setCompoundDrawablesWithIntrinsicBounds(
- 0, 0, sigActionIcon, 0);
+ holder.getSignatureAction().setDisplayedChild(sigActionDisplayedChild);
String userId = result.getSignatureResult().getPrimaryUserId();
KeyRing.UserId userIdSplit = KeyRing.splitUserId(userId);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Notify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Notify.java
index 7dfd56430..71f6ecc1a 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Notify.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Notify.java
@@ -37,7 +37,7 @@ import org.sufficientlysecure.keychain.util.FabContainer;
*/
public class Notify {
- public static enum Style {
+ public enum Style {
OK (R.color.android_green_light), WARN(R.color.android_orange_light), ERROR(R.color.android_red_light);
public final int mLineColor;
@@ -142,6 +142,11 @@ public class Notify {
return create(activity, text, LENGTH_LONG, style);
}
+ public static Showable create(Activity activity, int textResId, Style style,
+ ActionListener actionListener, int actionResId) {
+ return create(activity, textResId, LENGTH_LONG, style, actionListener, actionResId);
+ }
+
public static Showable create(Activity activity, int textResId, int duration, Style style,
ActionListener actionListener, int actionResId) {
return create(activity, activity.getString(textResId), duration, style, actionListener, actionResId);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java
index 48e6c2cee..01d51af48 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java
@@ -23,11 +23,13 @@ import android.database.Cursor;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;
+import android.support.annotation.NonNull;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -46,14 +48,14 @@ import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem;
import org.sufficientlysecure.keychain.util.Log;
-public class EncryptKeyCompletionView extends TokenCompleteTextView
+public class EncryptKeyCompletionView extends TokenCompleteTextView<KeyItem>
implements LoaderCallbacks<Cursor> {
public static final String ARG_QUERY = "query";
private KeyAdapter mAdapter;
private LoaderManager mLoaderManager;
- private String mPrefix;
+ private CharSequence mPrefix;
public EncryptKeyCompletionView(Context context) {
super(context);
@@ -79,30 +81,27 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView
}
@Override
- public void setPrefix(String p) {
+ public void setPrefix(CharSequence p) {
// this one is private in the superclass, but we need it here
mPrefix = p;
super.setPrefix(p);
}
@Override
- protected View getViewForObject(Object object) {
- if (object instanceof KeyItem) {
- LayoutInflater l = LayoutInflater.from(getContext());
- View view = l.inflate(R.layout.recipient_box_entry, null);
- ((TextView) view.findViewById(android.R.id.text1)).setText(((KeyItem) object).getReadableName());
- return view;
- }
- return null;
+ protected View getViewForObject(KeyItem keyItem) {
+ LayoutInflater l = LayoutInflater.from(getContext());
+ View view = l.inflate(R.layout.recipient_box_entry, null);
+ ((TextView) view.findViewById(android.R.id.text1)).setText(keyItem.getReadableName());
+ return view;
}
@Override
- protected Object defaultObject(String completionText) {
+ protected KeyItem defaultObject(String completionText) {
// TODO: We could try to automagically download the key if it's unknown but a key id
/*if (completionText.startsWith("0x")) {
}*/
- return "";
+ return null;
}
@Override
@@ -128,7 +127,7 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView
// These are the rows that we will retrieve.
Uri baseUri = KeyRings.buildUnifiedKeyRingsUri();
- String[] projection = KeyAdapter.getProjectionWith(new String[] {
+ String[] projection = KeyAdapter.getProjectionWith(new String[]{
KeychainContract.KeyRings.HAS_ENCRYPT,
});
@@ -136,18 +135,19 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView
+ KeyRings.IS_EXPIRED + " = 0 AND "
+ Tables.KEYS + "." + KeyRings.IS_REVOKED + " = 0";
- if (args != null && args.containsKey(ARG_QUERY)) {
- String query = args.getString(ARG_QUERY);
- mAdapter.setSearchQuery(query);
+ if (args == null || !args.containsKey(ARG_QUERY)) {
+ // mAdapter.setSearchQuery(null);
+ // return new CursorLoader(getContext(), baseUri, projection, where, null, null);
+ return null;
+ }
- where += " AND " + KeyRings.USER_ID + " LIKE ?";
+ String query = args.getString(ARG_QUERY);
+ mAdapter.setSearchQuery(query);
- return new CursorLoader(getContext(), baseUri, projection, where,
- new String[]{"%" + query + "%"}, null);
- }
+ where += " AND " + KeyRings.USER_ID + " LIKE ?";
- mAdapter.setSearchQuery(null);
- return new CursorLoader(getContext(), baseUri, projection, where, null, null);
+ return new CursorLoader(getContext(), baseUri, projection, where,
+ new String[]{"%" + query + "%"}, null);
}
@@ -169,6 +169,8 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView
super.showDropDown();
}
+
+
@Override
public void onFocusChanged(boolean hasFocus, int direction, Rect previous) {
super.onFocusChanged(hasFocus, direction, previous);
@@ -179,13 +181,18 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView
}
@Override
- protected void performFiltering(CharSequence text, int start, int end, int keyCode) {
+ protected void performFiltering(@NonNull CharSequence text, int start, int end, int keyCode) {
super.performFiltering(text, start, end, keyCode);
if (start < mPrefix.length()) {
start = mPrefix.length();
}
+ String query = text.subSequence(start, end).toString();
+ if (TextUtils.isEmpty(query) || query.length() < 2) {
+ mLoaderManager.destroyLoader(0);
+ return;
+ }
Bundle args = new Bundle();
- args.putString(ARG_QUERY, text.subSequence(start, end).toString());
+ args.putString(ARG_QUERY, query);
mLoaderManager.restartLoader(0, args, this);
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java
index 18e830139..a8274e45a 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java
@@ -31,6 +31,7 @@ import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
+import android.view.animation.Animation;
import android.widget.ViewAnimator;
import org.sufficientlysecure.keychain.R;
@@ -73,4 +74,29 @@ public class ToolableViewAnimator extends ViewAnimator {
}
super.addView(child, index, params);
}
+
+ @Override
+ public void setDisplayedChild(int whichChild) {
+ if (whichChild != getDisplayedChild()) {
+ super.setDisplayedChild(whichChild);
+ }
+ }
+
+ public void setDisplayedChild(int whichChild, boolean animate) {
+ if (animate) {
+ setDisplayedChild(whichChild);
+ return;
+ }
+
+ Animation savedInAnim = getInAnimation();
+ Animation savedOutAnim = getOutAnimation();
+ setInAnimation(null);
+ setOutAnimation(null);
+
+ setDisplayedChild(whichChild);
+
+ setInAnimation(savedInAnim);
+ setOutAnimation(savedOutAnim);
+ }
+
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java
index d7491ab26..a55249842 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/EmailKeyHelper.java
@@ -74,9 +74,9 @@ public class EmailKeyHelper {
// Try _hkp._tcp SRV record first
String[] mailparts = mail.split("@");
if (mailparts.length == 2) {
- HkpKeyserver hkp = HkpKeyserver.resolve(mailparts[1]);
+ HkpKeyserver hkp = HkpKeyserver.resolve(mailparts[1], proxy);
if (hkp != null) {
- keys.addAll(getEmailKeys(mail, hkp, proxy));
+ keys.addAll(getEmailKeys(mail, hkp));
}
}
@@ -84,18 +84,17 @@ public class EmailKeyHelper {
// Most users don't have the SRV record, so ask a default server as well
String server = Preferences.getPreferences(context).getPreferredKeyserver();
if (server != null) {
- HkpKeyserver hkp = new HkpKeyserver(server);
- keys.addAll(getEmailKeys(mail, hkp, proxy));
+ HkpKeyserver hkp = new HkpKeyserver(server, proxy);
+ keys.addAll(getEmailKeys(mail, hkp));
}
}
return keys;
}
- public static List<ImportKeysListEntry> getEmailKeys(String mail, Keyserver keyServer,
- Proxy proxy) {
+ public static List<ImportKeysListEntry> getEmailKeys(String mail, Keyserver keyServer) {
Set<ImportKeysListEntry> keys = new HashSet<>();
try {
- for (ImportKeysListEntry key : keyServer.search(mail, proxy)) {
+ for (ImportKeysListEntry key : keyServer.search(mail)) {
if (key.isRevoked() || key.isExpired()) continue;
for (String userId : key.getUserIds()) {
if (userId.toLowerCase().contains(mail.toLowerCase(Locale.ENGLISH))) {
@@ -103,8 +102,7 @@ public class EmailKeyHelper {
}
}
}
- } catch (Keyserver.QueryFailedException ignored) {
- } catch (Keyserver.QueryNeedsRepairException ignored) {
+ } catch (Keyserver.CloudSearchFailureException ignored) {
}
return new ArrayList<>(keys);
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java
deleted file mode 100644
index 45dc33906..000000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ExportHelper.java
+++ /dev/null
@@ -1,124 +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.util;
-
-
-import java.io.File;
-
-import android.content.Intent;
-import android.net.Uri;
-import android.support.v4.app.FragmentActivity;
-
-import org.sufficientlysecure.keychain.Constants;
-import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.operations.results.ExportResult;
-import org.sufficientlysecure.keychain.service.ExportKeyringParcel;
-import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
-
-public class ExportHelper
- implements CryptoOperationHelper.Callback <ExportKeyringParcel, ExportResult> {
- protected File mExportFile;
-
- FragmentActivity mActivity;
-
- private boolean mExportSecret;
- private long[] mMasterKeyIds;
-
- public ExportHelper(FragmentActivity activity) {
- super();
- this.mActivity = activity;
- }
-
- /** Show dialog where to export keys */
- public void showExportKeysDialog(final Long masterKeyId, final File exportFile,
- final boolean exportSecret) {
- mExportFile = exportFile;
-
- String title;
- if (masterKeyId == null) {
- // export all keys
- title = mActivity.getString(R.string.title_export_keys);
- } else {
- // export only key specified at data uri
- title = mActivity.getString(R.string.title_export_key);
- }
-
- String message;
- if (exportSecret) {
- message = mActivity.getString(masterKeyId == null
- ? R.string.specify_backup_dest_secret
- : R.string.specify_backup_dest_secret_single);
- } else {
- message = mActivity.getString(masterKeyId == null
- ? R.string.specify_backup_dest
- : R.string.specify_backup_dest_single);
- }
-
- FileHelper.saveDocumentDialog(new FileHelper.FileDialogCallback() {
- @Override
- public void onFileSelected(File file, boolean checked) {
- mExportFile = file;
- exportKeys(masterKeyId == null ? null : new long[] { masterKeyId }, exportSecret);
- }
- }, mActivity.getSupportFragmentManager(), title, message, exportFile, null);
- }
-
- // TODO: If ExportHelper requires pending data (see CryptoOPerationHelper), activities using
- // TODO: this class should be able to call mExportOpHelper.handleActivity
-
- /**
- * Export keys
- */
- public void exportKeys(long[] masterKeyIds, boolean exportSecret) {
- Log.d(Constants.TAG, "exportKeys started");
- mExportSecret = exportSecret;
- mMasterKeyIds = masterKeyIds; // if masterKeyIds is null it means export all
-
- CryptoOperationHelper<ExportKeyringParcel, ExportResult> exportOpHelper =
- new CryptoOperationHelper<>(1, mActivity, this, R.string.progress_exporting);
- exportOpHelper.cryptoOperation();
- }
-
- @Override
- public ExportKeyringParcel createOperationInput() {
- return new ExportKeyringParcel(mMasterKeyIds, mExportSecret, mExportFile.getAbsolutePath());
- }
-
- @Override
- final public void onCryptoOperationSuccess(ExportResult result) {
- // trigger scan of the created 'media' file so it shows up on MTP
- // http://stackoverflow.com/questions/13737261/nexus-4-not-showing-files-via-mtp
- mActivity.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(mExportFile)));
- result.createNotify(mActivity).show();
- }
-
- @Override
- public void onCryptoOperationCancelled() {
-
- }
-
- @Override
- public void onCryptoOperationError(ExportResult result) {
- result.createNotify(mActivity).show();
- }
-
- @Override
- public boolean onCryptoSetProgress(String msg, int progress, int max) {
- return false;
- }
-}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java
index 9fb362412..bae119700 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelper.java
@@ -17,8 +17,20 @@
package org.sufficientlysecure.keychain.util;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.security.SecureRandom;
+import java.text.DecimalFormat;
+
import android.annotation.TargetApi;
-import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
import android.content.Context;
@@ -30,30 +42,12 @@ import android.net.Uri;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.Environment;
-import android.os.Handler;
-import android.os.Message;
-import android.os.Messenger;
import android.provider.DocumentsContract;
import android.provider.OpenableColumns;
-import android.support.annotation.StringRes;
import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentManager;
import android.widget.Toast;
-import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
-import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment;
-
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-import java.text.DecimalFormat;
-
/** This class offers a number of helper functions for saving documents.
*
@@ -81,53 +75,31 @@ import java.text.DecimalFormat;
*/
public class FileHelper {
- public static void openDocument(Fragment fragment, Uri last, String mimeType, boolean multiple, int requestCode) {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
- openDocumentPreKitKat(fragment, last, mimeType, multiple, requestCode);
- } else {
- openDocumentKitKat(fragment, mimeType, multiple, requestCode);
- }
+ @TargetApi(VERSION_CODES.KITKAT)
+ public static void saveDocument(Fragment fragment, String targetName, int requestCode) {
+ saveDocument(fragment, targetName, "*/*", requestCode);
}
- public static void saveDocument(Fragment fragment, String targetName, Uri inputUri,
- @StringRes int title, @StringRes int message, int requestCode) {
- saveDocument(fragment, targetName, inputUri, "*/*", title, message, requestCode);
+ /** Opens the storage browser on Android 4.4 or later for saving a file. */
+ @TargetApi(VERSION_CODES.KITKAT)
+ public static void saveDocument(Fragment fragment, String suggestedName, String mimeType, int requestCode) {
+ Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+ intent.setType(mimeType);
+ // Note: This is not documented, but works: Show the Internal Storage menu item in the drawer!
+ intent.putExtra("android.content.extra.SHOW_ADVANCED", true);
+ intent.putExtra(Intent.EXTRA_TITLE, suggestedName);
+ fragment.startActivityForResult(intent, requestCode);
}
- public static void saveDocument(Fragment fragment, String targetName, Uri inputUri, String mimeType,
- @StringRes int title, @StringRes int message, int requestCode) {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
- saveDocumentDialog(fragment, targetName, inputUri, title, message, requestCode);
+ public static void openDocument(Fragment fragment, Uri last, String mimeType, boolean multiple, int requestCode) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ openDocumentKitKat(fragment, mimeType, multiple, requestCode);
} else {
- saveDocumentKitKat(fragment, mimeType, targetName, requestCode);
+ openDocumentPreKitKat(fragment, last, mimeType, multiple, requestCode);
}
}
- public static void saveDocumentDialog(final Fragment fragment, String targetName, Uri inputUri,
- @StringRes int title, @StringRes int message, final int requestCode) {
-
- saveDocumentDialog(fragment, targetName, inputUri, title, message, new FileDialogCallback() {
- // is this a good idea? seems hacky...
- @Override
- public void onFileSelected(File file, boolean checked) {
- Intent intent = new Intent();
- intent.setData(Uri.fromFile(file));
- fragment.onActivityResult(requestCode, Activity.RESULT_OK, intent);
- }
- });
- }
-
- public static void saveDocumentDialog(final Fragment fragment, String targetName, Uri inputUri,
- @StringRes int title, @StringRes int message, FileDialogCallback callback) {
-
- File file = inputUri == null ? null : new File(inputUri.getPath());
- File parentDir = file != null && file.exists() ? file.getParentFile() : Constants.Path.APP_DIR;
- File targetFile = new File(parentDir, targetName);
- saveDocumentDialog(callback, fragment.getActivity().getSupportFragmentManager(),
- fragment.getString(title), fragment.getString(message), targetFile, null);
-
- }
-
/** Opens the preferred installed file manager on Android and shows a toast
* if no manager is installed. */
private static void openDocumentPreKitKat(
@@ -157,51 +129,12 @@ public class FileHelper {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType(mimeType);
+ // Note: This is not documented, but works: Show the Internal Storage menu item in the drawer!
+ intent.putExtra("android.content.extra.SHOW_ADVANCED", true);
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, multiple);
fragment.startActivityForResult(intent, requestCode);
}
- /** Opens the storage browser on Android 4.4 or later for saving a file. */
- @TargetApi(Build.VERSION_CODES.KITKAT)
- public static void saveDocumentKitKat(Fragment fragment, String mimeType, String suggestedName, int requestCode) {
- Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
- intent.addCategory(Intent.CATEGORY_OPENABLE);
- intent.setType(mimeType);
- intent.putExtra("android.content.extra.SHOW_ADVANCED", true); // Note: This is not documented, but works
- intent.putExtra(Intent.EXTRA_TITLE, suggestedName);
- fragment.startActivityForResult(intent, requestCode);
- }
-
- public static void saveDocumentDialog(
- final FileDialogCallback callback, final FragmentManager fragmentManager,
- final String title, final String message, final File defaultFile,
- final String checkMsg) {
- // Message is received after file is selected
- Handler returnHandler = new Handler() {
- @Override
- public void handleMessage(Message message) {
- if (message.what == FileDialogFragment.MESSAGE_OKAY) {
- callback.onFileSelected(
- new File(message.getData().getString(FileDialogFragment.MESSAGE_DATA_FILE)),
- message.getData().getBoolean(FileDialogFragment.MESSAGE_DATA_CHECKED));
- }
- }
- };
-
- // Create a new Messenger for the communication back
- final Messenger messenger = new Messenger(returnHandler);
-
- DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() {
- @Override
- public void run() {
- FileDialogFragment fileDialog = FileDialogFragment.newInstance(messenger, title, message,
- defaultFile, checkMsg);
-
- fileDialog.show(fragmentManager, "fileDialog");
- }
- });
- }
-
public static String getFilename(Context context, Uri uri) {
String filename = null;
try {
@@ -228,6 +161,14 @@ public class FileHelper {
}
public static long getFileSize(Context context, Uri uri, long def) {
+ if ("file".equals(uri.getScheme())) {
+ long size = new File(uri.getPath()).length();
+ if (size == 0) {
+ size = def;
+ }
+ return size;
+ }
+
long size = def;
try {
Cursor cursor = context.getContentResolver().query(uri, new String[]{OpenableColumns.SIZE}, null, null, null);
@@ -322,6 +263,44 @@ public class FileHelper {
}
}
+ /**
+ * Deletes data at a URI securely by overwriting it with random data
+ * before deleting it. This method is fail-fast - if we can't securely
+ * delete the file, we don't delete it at all.
+ */
+ public static int deleteFileSecurely(Context context, Uri uri)
+ throws IOException {
+
+ ContentResolver resolver = context.getContentResolver();
+ long lengthLeft = FileHelper.getFileSize(context, uri);
+
+ if (lengthLeft == -1) {
+ throw new IOException("Error opening file!");
+ }
+
+ SecureRandom random = new SecureRandom();
+ byte[] randomData = new byte[1024];
+
+ OutputStream out = resolver.openOutputStream(uri, "w");
+ if (out == null) {
+ throw new IOException("Error opening file!");
+ }
+ out = new BufferedOutputStream(out);
+ while (lengthLeft > 0) {
+ random.nextBytes(randomData);
+ out.write(randomData, 0, lengthLeft > randomData.length ? randomData.length : (int) lengthLeft);
+ lengthLeft -= randomData.length;
+ }
+ out.close();
+
+ if ("file".equals(uri.getScheme())) {
+ return new File(uri.getPath()).delete() ? 1 : 0;
+ } else {
+ return resolver.delete(uri, null, null);
+ }
+
+ }
+
/** Checks if external storage is mounted if file is located on external storage. */
public static boolean isStorageMounted(String file) {
if (file.startsWith(Environment.getExternalStorageDirectory().getAbsolutePath())) {
@@ -333,7 +312,24 @@ public class FileHelper {
return true;
}
- public interface FileDialogCallback {
- void onFileSelected(File file, boolean checked);
+ /** A replacement for ContentResolver.openInputStream() that does not allow
+ * the usage of "file" Uris that point to private files owned by the
+ * application only, *on Lollipop devices*.
+ *
+ * The check will be performed on devices >= Lollipop only, which have the
+ * necessary API to stat filedescriptors.
+ *
+ * @see FileHelperLollipop
+ */
+ public static InputStream openInputStreamSafe(ContentResolver resolver, Uri uri)
+ throws FileNotFoundException {
+
+ // Not supported on Android < 5
+ if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
+ return FileHelperLollipop.openInputStreamSafe(resolver, uri);
+ } else {
+ return resolver.openInputStream(uri);
+ }
}
+
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelperLollipop.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelperLollipop.java
new file mode 100644
index 000000000..f89d679bc
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FileHelperLollipop.java
@@ -0,0 +1,82 @@
+package org.sufficientlysecure.keychain.util;
+
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+import android.annotation.TargetApi;
+import android.content.ContentResolver;
+import android.content.res.AssetFileDescriptor;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
+import android.os.ParcelFileDescriptor;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructStat;
+
+import org.sufficientlysecure.keychain.Constants;
+
+import static android.system.OsConstants.S_IROTH;
+
+
+/** FileHelper methods which use Lollipop-exclusive API.
+ * Some of the methods and static fields used here cause VerifyErrors because
+ * they do not exist in pre-lollipop API, so they must be kept in a
+ * lollipop-only class. All methods here should only be called by FileHelper,
+ * and consequently have package visibility.
+ */
+@TargetApi(VERSION_CODES.LOLLIPOP)
+class FileHelperLollipop {
+ /**
+ * Tests whether a file is readable by others
+ */
+ private static boolean S_IROTH(int mode) {
+ return (mode & S_IROTH) == S_IROTH;
+ }
+
+ /**
+ * A replacement for ContentResolver.openInputStream() that does not allow the usage of
+ * "file" Uris that point to private files owned by the application only.
+ *
+ * This is not allowed:
+ * am start -a android.intent.action.SEND -t text/plain -n
+ * "org.sufficientlysecure.keychain.debug/org.sufficientlysecure.keychain.ui.EncryptFilesActivity" --eu
+ * android.intent.extra.STREAM
+ * file:///data/data/org.sufficientlysecure.keychain.debug/databases/openkeychain.db
+ *
+ * @throws FileNotFoundException
+ */
+ static InputStream openInputStreamSafe(ContentResolver resolver, Uri uri)
+ throws FileNotFoundException {
+
+ String scheme = uri.getScheme();
+ if (ContentResolver.SCHEME_FILE.equals(scheme)) {
+ ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
+ new File(uri.getPath()), ParcelFileDescriptor.parseMode("r"));
+
+ try {
+ final StructStat st = Os.fstat(pfd.getFileDescriptor());
+ if (!S_IROTH(st.st_mode)) {
+ Log.e(Constants.TAG, "File is not readable by others, aborting!");
+ throw new FileNotFoundException("Unable to create stream");
+ }
+ } catch (ErrnoException e) {
+ Log.e(Constants.TAG, "fstat() failed: " + e);
+ throw new FileNotFoundException("fstat() failed");
+ }
+
+ AssetFileDescriptor fd = new AssetFileDescriptor(pfd, 0, -1);
+ try {
+ return fd.createInputStream();
+ } catch (IOException e) {
+ throw new FileNotFoundException("Unable to create stream");
+ }
+ } else {
+ return resolver.openInputStream(uri);
+ }
+
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FilterCursorWrapper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FilterCursorWrapper.java
index ab73f59b8..d06f2ab65 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FilterCursorWrapper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/FilterCursorWrapper.java
@@ -1,3 +1,20 @@
+/*
+ * 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;
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 2b47fd623..af4e0d4f8 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/NfcHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/NfcHelper.java
@@ -98,7 +98,7 @@ public class NfcHelper {
* guarantee that this activity starts when receiving a beamed message. For now, this code
* uses the tag dispatch system.
*/
- return new NdefMessage(NdefRecord.createMime(Constants.NFC_MIME,
+ return new NdefMessage(NdefRecord.createMime(Constants.MIME_TYPE_KEYS,
mNfcKeyringBytes), NdefRecord.createApplicationRecord(Constants.PACKAGE_NAME));
}
};
@@ -141,6 +141,10 @@ public class NfcHelper {
}
protected void onPostExecute(Void unused) {
+ if (mActivity.isFinishing()) {
+ return;
+ }
+
// Register callback to set NDEF message
mNfcAdapter.setNdefPushMessageCallback(mNdefCallback,
mActivity);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java
new file mode 100644
index 000000000..d2c90cfcd
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/OkHttpKeybaseClient.java
@@ -0,0 +1,76 @@
+/*
+ * 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 com.squareup.okhttp.OkHttpClient;
+import com.squareup.okhttp.OkUrlFactory;
+import com.textuality.keybase.lib.KeybaseUrlConnectionClient;
+
+import org.sufficientlysecure.keychain.Constants;
+
+import java.io.IOException;
+import java.net.Proxy;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Wrapper for Keybase Lib
+ */
+public class OkHttpKeybaseClient implements KeybaseUrlConnectionClient {
+
+ private OkUrlFactory generateUrlFactory() {
+ OkHttpClient client = new OkHttpClient();
+ return new OkUrlFactory(client);
+ }
+
+ @Override
+ public URLConnection openConnection(URL url, Proxy proxy, boolean isKeybase) throws IOException {
+ OkUrlFactory factory = generateUrlFactory();
+ if (proxy != null) {
+ factory.client().setProxy(proxy);
+ factory.client().setConnectTimeout(30000, TimeUnit.MILLISECONDS);
+ factory.client().setReadTimeout(40000, TimeUnit.MILLISECONDS);
+ } else {
+ factory.client().setConnectTimeout(5000, TimeUnit.MILLISECONDS);
+ factory.client().setReadTimeout(25000, TimeUnit.MILLISECONDS);
+ }
+
+ factory.client().setFollowSslRedirects(false);
+
+ // forced the usage of api.keybase.io pinned certificate
+ if (isKeybase) {
+ try {
+ if (!TlsHelper.usePinnedCertificateIfAvailable(factory.client(), url)) {
+ throw new IOException("no pinned certificate found for URL!");
+ }
+ } catch (TlsHelper.TlsHelperException e) {
+ Log.e(Constants.TAG, "TlsHelper failed", e);
+ throw new IOException("TlsHelper failed");
+ }
+ }
+
+ return factory.open(url);
+ }
+
+ @Override
+ public String getKeybaseBaseUrl() {
+ return "https://api.keybase.io/";
+ }
+
+} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableProxy.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableProxy.java
index 7e788d04c..7e2328e99 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableProxy.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ParcelableProxy.java
@@ -17,12 +17,14 @@
package org.sufficientlysecure.keychain.util;
-import android.os.Parcel;
-import android.os.Parcelable;
import java.net.InetSocketAddress;
import java.net.Proxy;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.NonNull;
+
/**
* used to simply transport java.net.Proxy objects created using InetSockets between services/activities
*/
@@ -47,9 +49,10 @@ public class ParcelableProxy implements Parcelable {
return new ParcelableProxy(null, -1, null);
}
+ @NonNull
public Proxy getProxy() {
if (mProxyHost == null) {
- return null;
+ return Proxy.NO_PROXY;
}
/*
* InetSocketAddress.createUnresolved so we can use this method even in the main thread
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 4ef215036..559c5556f 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java
@@ -23,6 +23,9 @@ import android.content.SharedPreferences;
import android.content.res.Resources;
import android.preference.PreferenceManager;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Constants.Pref;
import org.sufficientlysecure.keychain.service.KeyserverSyncAdapterService;
@@ -123,16 +126,6 @@ public class Preferences {
return mSharedPreferences.getBoolean(Constants.Pref.FIRST_TIME, true);
}
- public boolean useDefaultYubiKeyPin() {
- return mSharedPreferences.getBoolean(Pref.USE_DEFAULT_YUBIKEY_PIN, false);
- }
-
- public void setUseDefaultYubiKeyPin(boolean useDefaultYubikeyPin) {
- SharedPreferences.Editor editor = mSharedPreferences.edit();
- editor.putBoolean(Pref.USE_DEFAULT_YUBIKEY_PIN, useDefaultYubikeyPin);
- editor.commit();
- }
-
public boolean useNumKeypadForYubiKeyPin() {
return mSharedPreferences.getBoolean(Pref.USE_NUMKEYPAD_FOR_YUBIKEY_PIN, true);
}
@@ -332,6 +325,12 @@ public class Preferences {
if (!torEnabled && !normalPorxyEnabled) this.parcelableProxy = new ParcelableProxy(null, -1, null);
else this.parcelableProxy = new ParcelableProxy(hostName, port, type);
}
+
+ @NonNull
+ public Proxy getProxy() {
+ return parcelableProxy.getProxy();
+ }
+
}
// cloud prefs
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ShareHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ShareHelper.java
deleted file mode 100644
index 0297d149c..000000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ShareHelper.java
+++ /dev/null
@@ -1,104 +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.util;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.LabeledIntent;
-import android.content.pm.ResolveInfo;
-import android.os.Build;
-import android.os.Parcelable;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
-public class ShareHelper {
- Context mContext;
-
- public ShareHelper(Context context) {
- mContext = context;
- }
-
- /**
- * Create Intent Chooser but exclude specific activites, e.g., EncryptActivity to prevent encrypting again
- * <p/>
- * Put together from some stackoverflow posts...
- */
- public Intent createChooserExcluding(Intent prototype, String title, String[] activityBlacklist) {
- // Produced an empty list on Huawei U8860 with Android Version 4.0.3
- // TODO: test on 4.1, 4.2, 4.3, only tested on 4.4
- // Disabled on 5.0 because using EXTRA_INITIAL_INTENTS prevents the usage based sorting
- // introduced in 5.0: https://medium.com/@xXxXxXxXxXam/how-lollipops-share-menu-is-organized-d204888f606d
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT || Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- return Intent.createChooser(prototype, title);
- }
-
- List<LabeledIntent> targetedShareIntents = new ArrayList<>();
-
- List<ResolveInfo> resInfoList = mContext.getPackageManager().queryIntentActivities(prototype, 0);
- List<ResolveInfo> resInfoListFiltered = new ArrayList<>();
- if (!resInfoList.isEmpty()) {
- for (ResolveInfo resolveInfo : resInfoList) {
- // do not add blacklisted ones
- if (resolveInfo.activityInfo == null || Arrays.asList(activityBlacklist).contains(resolveInfo.activityInfo.name))
- continue;
-
- resInfoListFiltered.add(resolveInfo);
- }
-
- if (!resInfoListFiltered.isEmpty()) {
- // sorting for nice readability
- Collections.sort(resInfoListFiltered, new Comparator<ResolveInfo>() {
- @Override
- public int compare(ResolveInfo first, ResolveInfo second) {
- String firstName = first.loadLabel(mContext.getPackageManager()).toString();
- String secondName = second.loadLabel(mContext.getPackageManager()).toString();
- return firstName.compareToIgnoreCase(secondName);
- }
- });
-
- // create the custom intent list
- for (ResolveInfo resolveInfo : resInfoListFiltered) {
- Intent targetedShareIntent = (Intent) prototype.clone();
- targetedShareIntent.setPackage(resolveInfo.activityInfo.packageName);
- targetedShareIntent.setClassName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name);
-
- LabeledIntent lIntent = new LabeledIntent(targetedShareIntent,
- resolveInfo.activityInfo.packageName,
- resolveInfo.loadLabel(mContext.getPackageManager()),
- resolveInfo.activityInfo.icon);
- targetedShareIntents.add(lIntent);
- }
-
- // Create chooser with only one Intent in it
- Intent chooserIntent = Intent.createChooser(targetedShareIntents.remove(targetedShareIntents.size() - 1), title);
- // append all other Intents
- // TODO this line looks wrong?!
- chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedShareIntents.toArray(new Parcelable[]{}));
- return chooserIntent;
- }
-
- }
-
- // fallback to Android's default chooser
- return Intent.createChooser(prototype, title);
- }
-}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java
index d1d1ada2a..1492abdeb 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/TlsHelper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2013-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
@@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.util;
import android.content.res.AssetManager;
import com.squareup.okhttp.OkHttpClient;
+
import org.sufficientlysecure.keychain.Constants;
import java.io.ByteArrayInputStream;
@@ -37,7 +38,6 @@ import java.security.cert.CertificateFactory;
import java.util.HashMap;
import java.util.Map;
-import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
@@ -49,15 +49,14 @@ public class TlsHelper {
}
}
- private static Map<String, byte[]> sStaticCA = new HashMap<>();
-
- public static void addStaticCA(String domain, byte[] certificate) {
- sStaticCA.put(domain, certificate);
- }
+ private static Map<String, byte[]> sPinnedCertificates = new HashMap<>();
- public static void addStaticCA(String domain, AssetManager assetManager, String name) {
+ /**
+ * Add certificate from assets to pinned certificate map.
+ */
+ public static void addPinnedCertificate(String host, AssetManager assetManager, String cerFilename) {
try {
- InputStream is = assetManager.open(name);
+ InputStream is = assetManager.open(cerFilename);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int reads = is.read();
@@ -68,27 +67,36 @@ public class TlsHelper {
is.close();
- addStaticCA(domain, baos.toByteArray());
+ sPinnedCertificates.put(host, baos.toByteArray());
} catch (IOException e) {
Log.w(Constants.TAG, e);
}
}
- public static void pinCertificateIfNecessary(OkHttpClient client, URL url) throws TlsHelperException, IOException {
+ /**
+ * Use pinned certificate for OkHttpClient if we have one.
+ *
+ * @return true, if certificate is available, false if not
+ * @throws TlsHelperException
+ * @throws IOException
+ */
+ public static boolean usePinnedCertificateIfAvailable(OkHttpClient client, URL url) throws TlsHelperException, IOException {
if (url.getProtocol().equals("https")) {
- for (String domain : sStaticCA.keySet()) {
- if (url.getHost().endsWith(domain)) {
- pinCertificate(sStaticCA.get(domain), client);
+ // use certificate PIN from assets if we have one
+ for (String host : sPinnedCertificates.keySet()) {
+ if (url.getHost().endsWith(host)) {
+ pinCertificate(sPinnedCertificates.get(host), client);
+ return true;
}
}
}
+ return false;
}
/**
* Modifies the client to accept only requests with a given certificate. Applies to all URLs requested by the
* client.
* Therefore a client that is pinned this way should be used to only make requests to URLs with passed certificate.
- * TODO: Refactor - More like SSH StrictHostKeyChecking than pinning?
*
* @param certificate certificate to pin
* @param client OkHttpClient to enforce pinning on
@@ -97,8 +105,10 @@ public class TlsHelper {
*/
private static void pinCertificate(byte[] certificate, OkHttpClient client)
throws TlsHelperException, IOException {
- // We don't use OkHttp's CertificatePinner since it depends on a TrustManager to verify it too. Refer to
- // note at end of description: http://square.github.io/okhttp/javadoc/com/squareup/okhttp/CertificatePinner.html
+ // We don't use OkHttp's CertificatePinner since it can not be used to pin self-signed
+ // certificate if such certificate is not accepted by TrustManager.
+ // (Refer to note at end of description:
+ // http://square.github.io/okhttp/javadoc/com/squareup/okhttp/CertificatePinner.html )
// Creating our own TrustManager that trusts only our certificate eliminates the need for certificate pinning
try {
// Load CA
@@ -126,42 +136,4 @@ public class TlsHelper {
}
}
- /**
- * Opens a Connection that will only accept certificates signed with a specific CA and skips common name check.
- * This is required for some distributed Keyserver networks like sks-keyservers.net
- *
- * @param certificate The X.509 certificate used to sign the servers certificate
- * @param url Connection target
- */
- public static HttpsURLConnection openCAConnection(byte[] certificate, URL url)
- throws TlsHelperException, IOException {
- try {
- // Load CA
- CertificateFactory cf = CertificateFactory.getInstance("X.509");
- Certificate ca = cf.generateCertificate(new ByteArrayInputStream(certificate));
-
- // Create a KeyStore containing our trusted CAs
- String keyStoreType = KeyStore.getDefaultType();
- KeyStore keyStore = KeyStore.getInstance(keyStoreType);
- keyStore.load(null, null);
- keyStore.setCertificateEntry("ca", ca);
-
- // Create a TrustManager that trusts the CAs in our KeyStore
- String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
- TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
- tmf.init(keyStore);
-
- // Create an SSLContext that uses our TrustManager
- SSLContext context = SSLContext.getInstance("TLS");
- context.init(null, tmf.getTrustManagers(), null);
-
- // Tell the URLConnection to use a SocketFactory from our SSLContext
- HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
- urlConnection.setSSLSocketFactory(context.getSocketFactory());
-
- return urlConnection;
- } catch (CertificateException | KeyManagementException | KeyStoreException | NoSuchAlgorithmException e) {
- throw new TlsHelperException(e);
- }
- }
}