aboutsummaryrefslogtreecommitdiffstats
path: root/OpenKeychain
diff options
context:
space:
mode:
Diffstat (limited to 'OpenKeychain')
-rw-r--r--OpenKeychain/src/main/AndroidManifest.xml6
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/KeyUpdateHelper.java86
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java4
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java249
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java68
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java12
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java41
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileActivity.java24
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java23
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java36
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java367
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java8
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java31
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ListAwareSwipeRefreshLayout.java81
-rw-r--r--OpenKeychain/src/main/res/layout/key_list_fragment.xml146
-rw-r--r--OpenKeychain/src/main/res/layout/view_key_main_fragment.xml1
17 files changed, 920 insertions, 265 deletions
diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml
index 35f66c012..a83992d09 100644
--- a/OpenKeychain/src/main/AndroidManifest.xml
+++ b/OpenKeychain/src/main/AndroidManifest.xml
@@ -585,8 +585,10 @@
</activity>
<activity
android:name=".ui.ConsolidateDialogActivity"
- android:theme="@android:style/Theme.NoDisplay"
- android:label="@string/app_name" />
+ android:theme="@android:style/Theme.NoDisplay" />
+ <activity
+ android:name=".ui.PassphraseDialogActivity"
+ android:theme="@android:style/Theme.NoDisplay" />
<activity
android:name=".ui.HelpActivity"
android:label="@string/title_help" />
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/KeyUpdateHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/KeyUpdateHelper.java
new file mode 100644
index 000000000..9ca6d5c5b
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/KeyUpdateHelper.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2014 Daniel Albert
+ *
+ * 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.helper;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Messenger;
+
+import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
+import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
+import org.sufficientlysecure.keychain.keyimport.Keyserver;
+import org.sufficientlysecure.keychain.provider.KeychainContract;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.service.KeychainIntentService;
+import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
+import org.sufficientlysecure.keychain.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class KeyUpdateHelper {
+
+ public void updateAllKeys(Context context, KeychainIntentServiceHandler finishedHandler) {
+ UpdateTask updateTask = new UpdateTask(context, finishedHandler);
+ updateTask.execute();
+ }
+
+ private class UpdateTask extends AsyncTask<Void, Void, Void> {
+ private Context mContext;
+ private KeychainIntentServiceHandler mHandler;
+
+ public UpdateTask(Context context, KeychainIntentServiceHandler handler) {
+ this.mContext = context;
+ this.mHandler = handler;
+ }
+
+ @Override
+ protected Void doInBackground(Void... voids) {
+ ProviderHelper providerHelper = new ProviderHelper(mContext);
+ List<ImportKeysListEntry> keys = new ArrayList<ImportKeysListEntry>();
+ String[] servers = Preferences.getPreferences(mContext).getKeyServers();
+
+ if (servers != null && servers.length > 0) {
+ // Load all the fingerprints in the database and prepare to import them
+ for (String fprint : providerHelper.getAllFingerprints(KeychainContract.KeyRings.buildUnifiedKeyRingsUri())) {
+ ImportKeysListEntry key = new ImportKeysListEntry();
+ key.setFingerprintHex(fprint);
+ key.setBitStrength(1337);
+ key.setOrigin(servers[0]);
+ keys.add(key);
+ }
+
+ // Start the service and update the keys
+ Intent importIntent = new Intent(mContext, KeychainIntentService.class);
+ importIntent.setAction(KeychainIntentService.ACTION_DOWNLOAD_AND_IMPORT_KEYS);
+
+ Bundle importData = new Bundle();
+ importData.putParcelableArrayList(KeychainIntentService.DOWNLOAD_KEY_LIST,
+ new ArrayList<ImportKeysListEntry>(keys));
+ importIntent.putExtra(KeychainIntentService.EXTRA_DATA, importData);
+
+ importIntent.putExtra(KeychainIntentService.EXTRA_MESSENGER, new Messenger(mHandler));
+
+ mContext.startService(importIntent);
+ }
+ return null;
+ }
+ }
+}
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 0d3e5676a..554899843 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java
@@ -59,6 +59,10 @@ public abstract class CanonicalizedKeyRing extends KeyRing {
return getPublicKey().getPrimaryUserIdWithFallback();
}
+ public ArrayList<byte[]> getUnorderedRawUserIds() {
+ return getPublicKey().getUnorderedRawUserIds();
+ }
+
public ArrayList<String> getUnorderedUserIds() {
return getPublicKey().getUnorderedUserIds();
}
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 4239a01dc..fe4e7ffa7 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java
@@ -33,6 +33,8 @@ import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureList;
import org.spongycastle.openpgp.PGPUtil;
import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
+import org.spongycastle.util.Strings;
+import org.spongycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.service.results.OperationResultParcel.LogLevel;
@@ -361,156 +363,159 @@ public class UncachedKeyRing {
}
}
- ArrayList<String> processedUserIds = new ArrayList<String>();
- for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) {
+ ArrayList<byte[]> processedUserIds = new ArrayList<byte[]>();
+ for (byte[] rawUserId : new IterableIterator<byte[]>(masterKey.getRawUserIDs())) {
+ String userId = Strings.fromUTF8ByteArray(rawUserId);
+
// check for duplicate user ids
- if (processedUserIds.contains(userId)) {
+ if (processedUserIds.contains(rawUserId)) {
log.add(LogLevel.WARN, LogType.MSG_KC_UID_DUP,
indent, userId);
// strip out the first found user id with this name
- modified = PGPPublicKey.removeCertification(modified, userId);
+ modified = PGPPublicKey.removeCertification(modified, rawUserId);
}
- processedUserIds.add(userId);
+ processedUserIds.add(rawUserId);
PGPSignature selfCert = null;
revocation = null;
- // look through signatures for this specific key
- for (PGPSignature zert : new IterableIterator<PGPSignature>(
- masterKey.getSignaturesForID(userId))) {
- WrappedSignature cert = new WrappedSignature(zert);
- long certId = cert.getKeyId();
-
- int type = zert.getSignatureType();
- if (type != PGPSignature.DEFAULT_CERTIFICATION
- && type != PGPSignature.NO_CERTIFICATION
- && type != PGPSignature.CASUAL_CERTIFICATION
- && type != PGPSignature.POSITIVE_CERTIFICATION
- && type != PGPSignature.CERTIFICATION_REVOCATION) {
- log.add(LogLevel.WARN, LogType.MSG_KC_UID_BAD_TYPE,
- indent, "0x" + Integer.toString(zert.getSignatureType(), 16));
- modified = PGPPublicKey.removeCertification(modified, userId, zert);
- badCerts += 1;
- continue;
- }
-
- if (cert.getCreationTime().after(now)) {
- // Creation date in the future? No way!
- log.add(LogLevel.WARN, LogType.MSG_KC_UID_BAD_TIME, indent);
- modified = PGPPublicKey.removeCertification(modified, userId, zert);
- badCerts += 1;
- continue;
- }
-
- if (cert.isLocal()) {
- // Creation date in the future? No way!
- log.add(LogLevel.WARN, LogType.MSG_KC_UID_BAD_LOCAL, indent);
- modified = PGPPublicKey.removeCertification(modified, userId, zert);
- badCerts += 1;
- continue;
- }
+ // look through signatures for this specific user id
+ @SuppressWarnings("unchecked")
+ Iterator<PGPSignature> signaturesIt = masterKey.getSignaturesForID(rawUserId);
+ if (signaturesIt != null) {
+ for (PGPSignature zert : new IterableIterator<PGPSignature>(signaturesIt)) {
+ WrappedSignature cert = new WrappedSignature(zert);
+ long certId = cert.getKeyId();
+
+ int type = zert.getSignatureType();
+ if (type != PGPSignature.DEFAULT_CERTIFICATION
+ && type != PGPSignature.NO_CERTIFICATION
+ && type != PGPSignature.CASUAL_CERTIFICATION
+ && type != PGPSignature.POSITIVE_CERTIFICATION
+ && type != PGPSignature.CERTIFICATION_REVOCATION) {
+ log.add(LogLevel.WARN, LogType.MSG_KC_UID_BAD_TYPE,
+ indent, "0x" + Integer.toString(zert.getSignatureType(), 16));
+ modified = PGPPublicKey.removeCertification(modified, rawUserId, zert);
+ badCerts += 1;
+ continue;
+ }
- // If this is a foreign signature, ...
- if (certId != masterKeyId) {
- // never mind any further for public keys, but remove them from secret ones
- if (isSecret()) {
- log.add(LogLevel.WARN, LogType.MSG_KC_UID_FOREIGN,
- indent, PgpKeyHelper.convertKeyIdToHex(certId));
- modified = PGPPublicKey.removeCertification(modified, userId, zert);
+ if (cert.getCreationTime().after(now)) {
+ // Creation date in the future? No way!
+ log.add(LogLevel.WARN, LogType.MSG_KC_UID_BAD_TIME, indent);
+ modified = PGPPublicKey.removeCertification(modified, rawUserId, zert);
badCerts += 1;
+ continue;
}
- continue;
- }
- // Otherwise, first make sure it checks out
- try {
- cert.init(masterKey);
- if (!cert.verifySignature(masterKey, userId)) {
- log.add(LogLevel.WARN, LogType.MSG_KC_UID_BAD,
- indent, userId);
- modified = PGPPublicKey.removeCertification(modified, userId, zert);
+ if (cert.isLocal()) {
+ // Creation date in the future? No way!
+ log.add(LogLevel.WARN, LogType.MSG_KC_UID_BAD_LOCAL, indent);
+ modified = PGPPublicKey.removeCertification(modified, rawUserId, zert);
badCerts += 1;
continue;
}
- } catch (PgpGeneralException e) {
- log.add(LogLevel.WARN, LogType.MSG_KC_UID_BAD_ERR,
- indent, userId);
- modified = PGPPublicKey.removeCertification(modified, userId, zert);
- badCerts += 1;
- continue;
- }
- switch (type) {
- case PGPSignature.DEFAULT_CERTIFICATION:
- case PGPSignature.NO_CERTIFICATION:
- case PGPSignature.CASUAL_CERTIFICATION:
- case PGPSignature.POSITIVE_CERTIFICATION:
- if (selfCert == null) {
- selfCert = zert;
- } else if (selfCert.getCreationTime().before(cert.getCreationTime())) {
- log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_CERT_DUP,
- indent, userId);
- modified = PGPPublicKey.removeCertification(modified, userId, selfCert);
- redundantCerts += 1;
- selfCert = zert;
- } else {
- log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_CERT_DUP,
- indent, userId);
- modified = PGPPublicKey.removeCertification(modified, userId, zert);
- redundantCerts += 1;
- }
- // If there is a revocation certificate, and it's older than this, drop it
- if (revocation != null
- && revocation.getCreationTime().before(selfCert.getCreationTime())) {
- log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_REVOKE_OLD,
- indent, userId);
- modified = PGPPublicKey.removeCertification(modified, userId, revocation);
- revocation = null;
- redundantCerts += 1;
+ // If this is a foreign signature, ...
+ if (certId != masterKeyId) {
+ // never mind any further for public keys, but remove them from secret ones
+ if (isSecret()) {
+ log.add(LogLevel.WARN, LogType.MSG_KC_UID_FOREIGN,
+ indent, PgpKeyHelper.convertKeyIdToHex(certId));
+ modified = PGPPublicKey.removeCertification(modified, rawUserId, zert);
+ badCerts += 1;
}
- break;
+ continue;
+ }
- case PGPSignature.CERTIFICATION_REVOCATION:
- // If this is older than the (latest) self cert, drop it
- if (selfCert != null && selfCert.getCreationTime().after(zert.getCreationTime())) {
- log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_REVOKE_OLD,
+ // Otherwise, first make sure it checks out
+ try {
+ cert.init(masterKey);
+ if (!cert.verifySignature(masterKey, rawUserId)) {
+ log.add(LogLevel.WARN, LogType.MSG_KC_UID_BAD,
indent, userId);
- modified = PGPPublicKey.removeCertification(modified, userId, zert);
- redundantCerts += 1;
+ modified = PGPPublicKey.removeCertification(modified, rawUserId, zert);
+ badCerts += 1;
continue;
}
- // first revocation? remember it.
- if (revocation == null) {
- revocation = zert;
- // more revocations? at least one is superfluous, then.
- } else if (revocation.getCreationTime().before(cert.getCreationTime())) {
- log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_REVOKE_DUP,
- indent, userId);
- modified = PGPPublicKey.removeCertification(modified, userId, revocation);
- redundantCerts += 1;
- revocation = zert;
- } else {
- log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_REVOKE_DUP,
- indent, userId);
- modified = PGPPublicKey.removeCertification(modified, userId, zert);
- redundantCerts += 1;
- }
- break;
+ } catch (PgpGeneralException e) {
+ log.add(LogLevel.WARN, LogType.MSG_KC_UID_BAD_ERR,
+ indent, userId);
+ modified = PGPPublicKey.removeCertification(modified, rawUserId, zert);
+ badCerts += 1;
+ continue;
+ }
+ switch (type) {
+ case PGPSignature.DEFAULT_CERTIFICATION:
+ case PGPSignature.NO_CERTIFICATION:
+ case PGPSignature.CASUAL_CERTIFICATION:
+ case PGPSignature.POSITIVE_CERTIFICATION:
+ if (selfCert == null) {
+ selfCert = zert;
+ } else if (selfCert.getCreationTime().before(cert.getCreationTime())) {
+ log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_CERT_DUP,
+ indent, userId);
+ modified = PGPPublicKey.removeCertification(modified, rawUserId, selfCert);
+ redundantCerts += 1;
+ selfCert = zert;
+ } else {
+ log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_CERT_DUP,
+ indent, userId);
+ modified = PGPPublicKey.removeCertification(modified, rawUserId, zert);
+ redundantCerts += 1;
+ }
+ // If there is a revocation certificate, and it's older than this, drop it
+ if (revocation != null
+ && revocation.getCreationTime().before(selfCert.getCreationTime())) {
+ log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_REVOKE_OLD,
+ indent, userId);
+ modified = PGPPublicKey.removeCertification(modified, rawUserId, revocation);
+ revocation = null;
+ redundantCerts += 1;
+ }
+ break;
+
+ case PGPSignature.CERTIFICATION_REVOCATION:
+ // If this is older than the (latest) self cert, drop it
+ if (selfCert != null && selfCert.getCreationTime().after(zert.getCreationTime())) {
+ log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_REVOKE_OLD,
+ indent, userId);
+ modified = PGPPublicKey.removeCertification(modified, rawUserId, zert);
+ redundantCerts += 1;
+ continue;
+ }
+ // first revocation? remember it.
+ if (revocation == null) {
+ revocation = zert;
+ // more revocations? at least one is superfluous, then.
+ } else if (revocation.getCreationTime().before(cert.getCreationTime())) {
+ log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_REVOKE_DUP,
+ indent, userId);
+ modified = PGPPublicKey.removeCertification(modified, rawUserId, revocation);
+ redundantCerts += 1;
+ revocation = zert;
+ } else {
+ log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_REVOKE_DUP,
+ indent, userId);
+ modified = PGPPublicKey.removeCertification(modified, rawUserId, zert);
+ redundantCerts += 1;
+ }
+ break;
+ }
}
-
}
// If no valid certificate (if only a revocation) remains, drop it
if (selfCert == null && revocation == null) {
log.add(LogLevel.ERROR, LogType.MSG_KC_UID_REMOVE,
indent, userId);
- modified = PGPPublicKey.removeCertification(modified, userId);
+ modified = PGPPublicKey.removeCertification(modified, rawUserId);
}
}
// If NO user ids remain, error out!
- if (!modified.getUserIDs().hasNext()) {
+ if (modified == null || !modified.getUserIDs().hasNext()) {
log.add(LogLevel.ERROR, LogType.MSG_KC_ERROR_NO_UID, indent);
return null;
}
@@ -816,8 +821,14 @@ public class UncachedKeyRing {
}
// Copy over all user id certificates
- for (String userId : new IterableIterator<String>(key.getUserIDs())) {
- for (PGPSignature cert : new IterableIterator<PGPSignature>(key.getSignaturesForID(userId))) {
+ for (byte[] rawUserId : new IterableIterator<byte[]>(key.getRawUserIDs())) {
+ @SuppressWarnings("unchecked")
+ Iterator<PGPSignature> signaturesIt = key.getSignaturesForID(rawUserId);
+ // no signatures for this User ID, skip it
+ if (signaturesIt == null) {
+ continue;
+ }
+ for (PGPSignature cert : new IterableIterator<PGPSignature>(signaturesIt)) {
// Don't merge foreign stuff into secret keys
if (cert.getKeyID() != masterKeyId && isSecret()) {
continue;
@@ -829,7 +840,7 @@ public class UncachedKeyRing {
}
newCerts += 1;
certs.add(encoded);
- modified = PGPPublicKey.addCertification(modified, userId, cert);
+ modified = PGPPublicKey.addCertification(modified, rawUserId, cert);
}
}
// If anything changed, save the updated (sub)key
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java
index 2224d7391..e27190bc7 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java
@@ -28,11 +28,13 @@ import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
+import org.spongycastle.util.Strings;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
@@ -126,12 +128,20 @@ public class UncachedPublicKey {
*
*/
public String getPrimaryUserId() {
- String found = null;
+ byte[] found = null;
PGPSignature foundSig = null;
- for (String userId : new IterableIterator<String>(mPublicKey.getUserIDs())) {
+ // noinspection unchecked
+ for (byte[] rawUserId : new IterableIterator<byte[]>(mPublicKey.getRawUserIDs())) {
PGPSignature revocation = null;
- for (PGPSignature sig : new IterableIterator<PGPSignature>(mPublicKey.getSignaturesForID(userId))) {
+ @SuppressWarnings("unchecked")
+ Iterator<PGPSignature> signaturesIt = mPublicKey.getSignaturesForID(rawUserId);
+ // no signatures for this User ID
+ if (signaturesIt == null) {
+ continue;
+ }
+
+ for (PGPSignature sig : new IterableIterator<PGPSignature>(signaturesIt)) {
try {
// if this is a revocation, this is not the user id
@@ -139,10 +149,10 @@ public class UncachedPublicKey {
// make sure it's actually valid
sig.init(new JcaPGPContentVerifierBuilderProvider().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME), mPublicKey);
- if (!sig.verifyCertification(userId, mPublicKey)) {
+ if (!sig.verifyCertification(rawUserId, mPublicKey)) {
continue;
}
- if (found != null && found.equals(userId)) {
+ if (found != null && Arrays.equals(found, rawUserId)) {
found = null;
}
revocation = sig;
@@ -161,8 +171,8 @@ public class UncachedPublicKey {
// make sure it's actually valid
sig.init(new JcaPGPContentVerifierBuilderProvider().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME), mPublicKey);
- if (sig.verifyCertification(userId, mPublicKey)) {
- found = userId;
+ if (sig.verifyCertification(rawUserId, mPublicKey)) {
+ found = rawUserId;
foundSig = sig;
// this one can't be relevant anymore at this point
revocation = null;
@@ -174,7 +184,11 @@ public class UncachedPublicKey {
}
}
}
- return found;
+ if (found != null) {
+ return Strings.fromUTF8ByteArray(found);
+ } else {
+ return null;
+ }
}
/**
@@ -196,6 +210,14 @@ public class UncachedPublicKey {
return userIds;
}
+ public ArrayList<byte[]> getUnorderedRawUserIds() {
+ ArrayList<byte[]> userIds = new ArrayList<byte[]>();
+ for (byte[] userId : new IterableIterator<byte[]>(mPublicKey.getRawUserIDs())) {
+ userIds.add(userId);
+ }
+ return userIds;
+ }
+
public boolean isElGamalEncrypt() {
return getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT;
}
@@ -312,19 +334,23 @@ public class UncachedPublicKey {
};
}
- public Iterator<WrappedSignature> getSignaturesForId(String userId) {
- final Iterator<PGPSignature> it = mPublicKey.getSignaturesForID(userId);
- return new Iterator<WrappedSignature>() {
- public void remove() {
- it.remove();
- }
- public WrappedSignature next() {
- return new WrappedSignature(it.next());
- }
- public boolean hasNext() {
- return it.hasNext();
- }
- };
+ public Iterator<WrappedSignature> getSignaturesForRawId(byte[] rawUserId) {
+ final Iterator<PGPSignature> it = mPublicKey.getSignaturesForID(rawUserId);
+ if (it != null) {
+ return new Iterator<WrappedSignature>() {
+ public void remove() {
+ it.remove();
+ }
+ public WrappedSignature next() {
+ return new WrappedSignature(it.next());
+ }
+ public boolean hasNext() {
+ return it.hasNext();
+ }
+ };
+ } else {
+ return null;
+ }
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java
index f24259ba7..4d4c0e5d1 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java
@@ -187,8 +187,16 @@ public class WrappedSignature {
}
}
- public boolean verifySignature(UncachedPublicKey key, String uid) throws PgpGeneralException {
- return verifySignature(key.getPublicKey(), uid);
+ boolean verifySignature(PGPPublicKey key, byte[] rawUserId) throws PgpGeneralException {
+ try {
+ return mSig.verifyCertification(rawUserId, key);
+ } catch (PGPException e) {
+ throw new PgpGeneralException("Error!", e);
+ }
+ }
+
+ public boolean verifySignature(UncachedPublicKey key, byte[] rawUserId) throws PgpGeneralException {
+ return verifySignature(key.getPublicKey(), rawUserId);
}
public boolean verifySignature(CanonicalizedPublicKey key, String uid) throws PgpGeneralException {
return verifySignature(key.getPublicKey(), uid);
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 f71fcbd80..fdf8c1f38 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java
@@ -28,6 +28,7 @@ import android.net.Uri;
import android.os.RemoteException;
import android.support.v4.util.LongSparseArray;
+import org.spongycastle.util.Strings;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.Preferences;
@@ -154,7 +155,7 @@ public class ProviderHelper {
}
public HashMap<String, Object> getGenericData(Uri uri, String[] proj, int[] types)
- throws NotFoundException {
+ throws NotFoundException {
return getGenericData(uri, proj, types, null);
}
@@ -208,7 +209,7 @@ public class ProviderHelper {
KeyRings.HAS_ANY_SECRET, KeyRings.VERIFIED,
// and of course, ring data
KeyRings.PUBKEY_DATA
- }, KeyRings.HAS_ANY_SECRET + " = 1", null, null);
+ }, KeyRings.HAS_ANY_SECRET + " = 1", null, null);
try {
LongSparseArray<CanonicalizedPublicKey> result = new LongSparseArray<CanonicalizedPublicKey>();
@@ -406,11 +407,11 @@ public class ProviderHelper {
values.put(Keys.EXPIRY, expiryDate.getTime() / 1000);
if (key.isExpired()) {
log(LogLevel.DEBUG, keyId == masterKeyId ?
- LogType.MSG_IP_MASTER_EXPIRED : LogType.MSG_IP_SUBKEY_EXPIRED,
+ LogType.MSG_IP_MASTER_EXPIRED : LogType.MSG_IP_SUBKEY_EXPIRED,
expiryDate.toString());
} else {
log(LogLevel.DEBUG, keyId == masterKeyId ?
- LogType.MSG_IP_MASTER_EXPIRES : LogType.MSG_IP_SUBKEY_EXPIRES,
+ LogType.MSG_IP_MASTER_EXPIRES : LogType.MSG_IP_SUBKEY_EXPIRES,
expiryDate.toString());
}
}
@@ -434,8 +435,11 @@ public class ProviderHelper {
}
mIndent += 1;
List<UserIdItem> uids = new ArrayList<UserIdItem>();
- for (String userId : new IterableIterator<String>(
- masterKey.getUnorderedUserIds().iterator())) {
+ for (byte[] rawUserId : new IterableIterator<byte[]>(
+ masterKey.getUnorderedRawUserIds().iterator())) {
+ String userId = Strings.fromUTF8ByteArray(rawUserId);
+ Log.d(Constants.TAG, "userId: "+userId);
+
UserIdItem item = new UserIdItem();
uids.add(item);
item.userId = userId;
@@ -446,7 +450,7 @@ public class ProviderHelper {
mIndent += 1;
// look through signatures for this specific key
for (WrappedSignature cert : new IterableIterator<WrappedSignature>(
- masterKey.getSignaturesForId(userId))) {
+ masterKey.getSignaturesForRawId(rawUserId))) {
long certId = cert.getKeyId();
try {
// self signature
@@ -469,7 +473,7 @@ public class ProviderHelper {
if (trustedKeys.indexOfKey(certId) >= 0) {
CanonicalizedPublicKey trustedKey = trustedKeys.get(certId);
cert.init(trustedKey);
- if (cert.verifySignature(masterKey, userId)) {
+ if (cert.verifySignature(masterKey, rawUserId)) {
item.trustedCerts.add(cert);
log(LogLevel.INFO, LogType.MSG_IP_UID_CERT_GOOD,
PgpKeyHelper.convertKeyIdToHexShort(trustedKey.getKeyId())
@@ -1308,6 +1312,27 @@ public class ProviderHelper {
return keyIds;
}
+ public Set<String> getAllFingerprints(Uri uri) {
+ Set<String> fingerprints = new HashSet<String>();
+ String[] projection = new String[]{KeyRings.FINGERPRINT};
+ Cursor cursor = mContentResolver.query(uri, projection, null, null, null);
+ try {
+ if(cursor != null) {
+ int fingerprintColumn = cursor.getColumnIndex(KeyRings.FINGERPRINT);
+ while(cursor.moveToNext()) {
+ fingerprints.add(
+ PgpKeyHelper.convertFingerprintToHex(cursor.getBlob(fingerprintColumn))
+ );
+ }
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return fingerprints;
+ }
+
public byte[] getApiAppSignature(String packageName) {
Uri queryUri = ApiApps.buildByPackageNameUri(packageName);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java
index 7e8de7f01..ce5534cf4 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java
@@ -28,8 +28,6 @@ import android.support.v7.app.ActionBarActivity;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
-import android.text.SpannedString;
-import android.text.TextUtils;
import android.text.style.BulletSpan;
import android.text.style.StyleSpan;
import android.view.View;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileActivity.java
index 581145f8d..96f8bd983 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileActivity.java
@@ -452,27 +452,11 @@ public class EncryptFileActivity extends DrawerActivity implements EncryptActivi
* Android's Action
*/
+ // When sending to OpenKeychain Encrypt via share menu
if (Intent.ACTION_SEND.equals(action) && type != null) {
- // When sending to OpenKeychain Encrypt via share menu
- /* if ("text/plain".equals(type)) {
- // TODO handle, maybe forward to "encrypt text" activity?
-
- Plain text
- String sharedText = intent.getStringExtra(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);
- extras.putBoolean(EXTRA_ASCII_ARMOR, true);
- action = ACTION_ENCRYPT_TEXT;
- }
-
- } else */
- {
- // Files via content provider, override uri and action
- uris.clear();
- uris.add(intent.<Uri>getParcelableExtra(Intent.EXTRA_STREAM));
- }
+ // Files via content provider, override uri and action
+ uris.clear();
+ uris.add(intent.<Uri>getParcelableExtra(Intent.EXTRA_STREAM));
}
if (Intent.ACTION_SEND_MULTIPLE.equals(action) && type != null) {
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 bc0d3d387..6e6e52562 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java
@@ -409,16 +409,29 @@ public class EncryptTextActivity extends DrawerActivity implements EncryptActivi
private void handleActions(Intent intent) {
String action = intent.getAction();
Bundle extras = intent.getExtras();
- // Should always be text/plain
- // String type = intent.getType();
- ArrayList<Uri> uris = new ArrayList<Uri>();
+ String type = intent.getType();
if (extras == null) {
extras = new Bundle();
}
- if (intent.getData() != null) {
- uris.add(intent.getData());
+ /*
+ * Android's Action
+ */
+
+ // When sending to OpenKeychain Encrypt via share menu
+ if (Intent.ACTION_SEND.equals(action) && type != null) {
+ // When sending to OpenKeychain Encrypt via share menu
+ if ("text/plain".equals(type)) {
+ String sharedText = intent.getStringExtra(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);
+ action = ACTION_ENCRYPT_TEXT;
+ }
+
+ }
}
String textData = extras.getString(EXTRA_TEXT);
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 e7edc6058..4fda4cede 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java
@@ -35,6 +35,7 @@ import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.widget.CursorAdapter;
+import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.SearchView;
import android.view.ActionMode;
@@ -55,9 +56,13 @@ import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.ExportHelper;
+import org.sufficientlysecure.keychain.helper.KeyUpdateHelper;
+import org.sufficientlysecure.keychain.helper.Preferences;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
+import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
+import org.sufficientlysecure.keychain.ui.widget.ListAwareSwipeRefreshLayout;
import org.sufficientlysecure.keychain.util.Highlighter;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Notify;
@@ -74,10 +79,11 @@ import se.emilsjolander.stickylistheaders.StickyListHeadersListView;
*/
public class KeyListFragment extends LoaderFragment
implements SearchView.OnQueryTextListener, AdapterView.OnItemClickListener,
- LoaderManager.LoaderCallbacks<Cursor> {
+ LoaderManager.LoaderCallbacks<Cursor>, SwipeRefreshLayout.OnRefreshListener {
private KeyListAdapter mAdapter;
private StickyListHeadersListView mStickyList;
+ private ListAwareSwipeRefreshLayout mSwipeRefreshLayout;
// saves the mode object for multiselect, needed for reset at some point
private ActionMode mActionMode = null;
@@ -120,9 +126,25 @@ public class KeyListFragment extends LoaderFragment
}
});
+ mSwipeRefreshLayout = (ListAwareSwipeRefreshLayout) view.findViewById(R.id.key_list_swipe_container);
+ mSwipeRefreshLayout.setOnRefreshListener(this);
+ mSwipeRefreshLayout.setColorScheme(
+ R.color.android_purple_dark,
+ R.color.android_purple_light,
+ R.color.android_purple_dark,
+ R.color.android_purple_light);
+ mSwipeRefreshLayout.setStickyListHeadersListView(mStickyList);
+
return root;
}
+ @Override
+ public void onResume() {
+ String[] servers = Preferences.getPreferences(getActivity()).getKeyServers();
+ mSwipeRefreshLayout.setIsLocked(servers == null || servers.length == 0 || servers[0] == null);
+ super.onResume();
+ }
+
/**
* Define Adapter and Loader on create of Activity
*/
@@ -690,4 +712,16 @@ public class KeyListFragment extends LoaderFragment
}
+ /**
+ * Implements OnRefreshListener for drag-to-refresh
+ */
+ public void onRefresh() {
+ KeyUpdateHelper updateHelper = new KeyUpdateHelper();
+ KeychainIntentServiceHandler finishedHandler = new KeychainIntentServiceHandler(getActivity()) {
+ public void handleMessage(Message message) {
+ mSwipeRefreshLayout.setRefreshing(false);
+ }
+ };
+ updateHelper.updateAllKeys(getActivity(), finishedHandler);
+ }
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java
new file mode 100644
index 000000000..fa9444862
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java
@@ -0,0 +1,367 @@
+/*
+ * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.ui;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Messenger;
+import android.support.v4.app.DialogFragment;
+import android.support.v4.app.FragmentActivity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
+import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
+import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
+import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
+import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
+import org.sufficientlysecure.keychain.provider.KeychainContract;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.service.PassphraseCacheService;
+import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder;
+import org.sufficientlysecure.keychain.util.Log;
+
+/**
+ * We can not directly create a dialog on the application context.
+ * This activity encapsulates a DialogFragment to emulate a dialog.
+ */
+public class PassphraseDialogActivity extends FragmentActivity {
+ public static final String MESSAGE_DATA_PASSPHRASE = "passphrase";
+
+ public static final String EXTRA_SECRET_KEY_ID = "secret_key_id";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // do not allow screenshots of passphrase input
+ // to prevent "too easy" passphrase theft by root apps
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
+ getWindow().setFlags(
+ WindowManager.LayoutParams.FLAG_SECURE,
+ WindowManager.LayoutParams.FLAG_SECURE
+ );
+ }
+
+ // this activity itself has no content view (see manifest)
+
+ long keyId = getIntent().getLongExtra(EXTRA_SECRET_KEY_ID, 0);
+
+ show(this, keyId);
+ }
+
+ /**
+ * Shows passphrase dialog to cache a new passphrase the user enters for using it later for
+ * encryption. Based on mSecretKeyId it asks for a passphrase to open a private key or it asks
+ * for a symmetric passphrase
+ */
+ public static void show(final FragmentActivity context, final long keyId) {
+ DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() {
+ public void run() {
+ // do NOT check if the key even needs a passphrase. that's not our job here.
+ PassphraseDialogFragment frag = new PassphraseDialogFragment();
+ Bundle args = new Bundle();
+ args.putLong(EXTRA_SECRET_KEY_ID, keyId);
+
+ frag.setArguments(args);
+
+ frag.show(context.getSupportFragmentManager(), "passphraseDialog");
+ }
+ });
+ }
+
+ public static class PassphraseDialogFragment extends DialogFragment implements TextView.OnEditorActionListener {
+ private Messenger mMessenger;
+ private EditText mPassphraseEditText;
+ private View mInput, mProgress;
+
+ CanonicalizedSecretKeyRing mSecretRing = null;
+ boolean mIsCancelled = false;
+ long mSubKeyId;
+
+ /**
+ * Creates dialog
+ */
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Activity activity = getActivity();
+ mSubKeyId = getArguments().getLong(EXTRA_SECRET_KEY_ID);
+
+ CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity);
+
+ alert.setTitle(R.string.title_authentication);
+
+ String userId;
+
+ if (mSubKeyId == Constants.key.symmetric || mSubKeyId == Constants.key.none) {
+ alert.setMessage(R.string.passphrase_for_symmetric_encryption);
+ } else {
+ String message;
+ try {
+ ProviderHelper helper = new ProviderHelper(activity);
+ mSecretRing = helper.getCanonicalizedSecretKeyRing(
+ KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(mSubKeyId));
+ // yes the inner try/catch block is necessary, otherwise the final variable
+ // above can't be statically verified to have been set in all cases because
+ // the catch clause doesn't return.
+ try {
+ userId = mSecretRing.getPrimaryUserIdWithFallback();
+ } catch (PgpGeneralException e) {
+ userId = null;
+ }
+
+ /* Get key type for message */
+ // find a master key id for our key
+ long masterKeyId = new ProviderHelper(getActivity()).getMasterKeyId(mSubKeyId);
+ CachedPublicKeyRing keyRing = new ProviderHelper(getActivity()).getCachedPublicKeyRing(masterKeyId);
+ // get the type of key (from the database)
+ CanonicalizedSecretKey.SecretKeyType keyType = keyRing.getSecretKeyType(mSubKeyId);
+ switch (keyType) {
+ case PASSPHRASE:
+ message = getString(R.string.passphrase_for, userId);
+ break;
+ case DIVERT_TO_CARD:
+ message = getString(R.string.yubikey_pin, userId);
+ break;
+ default:
+ message = "This should not happen!";
+ break;
+ }
+
+ } catch (ProviderHelper.NotFoundException e) {
+ alert.setTitle(R.string.title_key_not_found);
+ alert.setMessage(getString(R.string.key_not_found, mSubKeyId));
+ alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ dismiss();
+ }
+ });
+ alert.setCancelable(false);
+ return alert.create();
+ }
+
+ alert.setMessage(message);
+ }
+
+ LayoutInflater inflater = activity.getLayoutInflater();
+ View view = inflater.inflate(R.layout.passphrase_dialog, null);
+ alert.setView(view);
+
+ mPassphraseEditText = (EditText) view.findViewById(R.id.passphrase_passphrase);
+ mInput = view.findViewById(R.id.input);
+ mProgress = view.findViewById(R.id.progress);
+
+ alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ }
+ });
+
+ // Hack to open keyboard.
+ // This is the only method that I found to work across all Android versions
+ // http://turbomanage.wordpress.com/2012/05/02/show-soft-keyboard-automatically-when-edittext-receives-focus/
+ // Notes: * onCreateView can't be used because we want to add buttons to the dialog
+ // * opening in onActivityCreated does not work on Android 4.4
+ mPassphraseEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ mPassphraseEditText.post(new Runnable() {
+ @Override
+ public void run() {
+ InputMethodManager imm = (InputMethodManager) getActivity()
+ .getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.showSoftInput(mPassphraseEditText, InputMethodManager.SHOW_IMPLICIT);
+ }
+ });
+ }
+ });
+ mPassphraseEditText.requestFocus();
+
+ mPassphraseEditText.setImeActionLabel(getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE);
+ mPassphraseEditText.setOnEditorActionListener(this);
+
+ AlertDialog dialog = alert.create();
+ dialog.setButton(DialogInterface.BUTTON_POSITIVE,
+ activity.getString(android.R.string.ok), (DialogInterface.OnClickListener) null);
+
+ return dialog;
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ // Override the default behavior so the dialog is NOT dismissed on click
+ final Button positive = ((AlertDialog) getDialog()).getButton(DialogInterface.BUTTON_POSITIVE);
+ positive.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final String passphrase = mPassphraseEditText.getText().toString();
+
+ // Early breakout if we are dealing with a symmetric key
+ if (mSecretRing == null) {
+ PassphraseCacheService.addCachedPassphrase(getActivity(), Constants.key.symmetric,
+ passphrase, getString(R.string.passp_cache_notif_pwd));
+ // also return passphrase back to activity
+ Intent returnIntent = new Intent();
+ returnIntent.putExtra(MESSAGE_DATA_PASSPHRASE, passphrase);
+ getActivity().setResult(RESULT_OK, returnIntent);
+ dismiss();
+ getActivity().finish();
+ return;
+ }
+
+ mInput.setVisibility(View.GONE);
+ mProgress.setVisibility(View.VISIBLE);
+ positive.setEnabled(false);
+
+ new AsyncTask<Void, Void, Boolean>() {
+ @Override
+ protected Boolean doInBackground(Void... params) {
+ try {
+ // wait some 100ms here, give the user time to appreciate the progress bar
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ // never mind
+ }
+ // make sure this unlocks
+ return mSecretRing.getSecretKey(mSubKeyId).unlock(passphrase);
+ } catch (PgpGeneralException e) {
+ Toast.makeText(getActivity(), R.string.error_could_not_extract_private_key,
+ Toast.LENGTH_SHORT).show();
+
+ getActivity().setResult(RESULT_CANCELED);
+ dismiss();
+ getActivity().finish();
+ return false;
+ }
+ }
+
+ /** Handle a good or bad passphrase. This happens in the UI thread! */
+ @Override
+ protected void onPostExecute(Boolean result) {
+ super.onPostExecute(result);
+
+ // if we were cancelled in the meantime, the result isn't relevant anymore
+ if (mIsCancelled) {
+ return;
+ }
+
+ // if the passphrase was wrong, reset and re-enable the dialogue
+ if (!result) {
+ mPassphraseEditText.setText("");
+ mPassphraseEditText.setError(getString(R.string.wrong_passphrase));
+ mInput.setVisibility(View.VISIBLE);
+ mProgress.setVisibility(View.GONE);
+ positive.setEnabled(true);
+ return;
+ }
+
+ // cache the new passphrase
+ Log.d(Constants.TAG, "Everything okay! Caching entered passphrase");
+
+ try {
+ PassphraseCacheService.addCachedPassphrase(getActivity(), mSubKeyId,
+ passphrase, mSecretRing.getPrimaryUserIdWithFallback());
+ } catch (PgpGeneralException e) {
+ Log.e(Constants.TAG, "adding of a passphrase failed", e);
+ }
+
+ // also return passphrase back to activity
+ Intent returnIntent = new Intent();
+ returnIntent.putExtra(MESSAGE_DATA_PASSPHRASE, passphrase);
+ getActivity().setResult(RESULT_OK, returnIntent);
+ dismiss();
+ getActivity().finish();
+ }
+ }.execute();
+ }
+ });
+
+ }
+
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ super.onCancel(dialog);
+
+ // note we need no synchronization here, this variable is only accessed in the ui thread
+ mIsCancelled = true;
+
+ // dismiss the dialogue
+ getActivity().setResult(RESULT_CANCELED);
+ dismiss();
+ getActivity().finish();
+ }
+
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ super.onDismiss(dialog);
+ Log.d(Constants.TAG, "onDismiss");
+
+ // hide keyboard on dismiss
+ hideKeyboard();
+ }
+
+ private void hideKeyboard() {
+ 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);
+ }
+
+ /**
+ * Associate the "done" button on the soft keyboard with the okay button in the view
+ */
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if (EditorInfo.IME_ACTION_DONE == actionId) {
+ AlertDialog dialog = ((AlertDialog) getDialog());
+ Button bt = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
+
+ bt.performClick();
+ return true;
+ }
+ return false;
+ }
+
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java
index e4b455481..f48006f08 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java
@@ -59,6 +59,7 @@ public class ViewKeyMainFragment extends LoaderFragment implements
private View mActionEditDivider;
private View mActionEncryptFiles;
private View mActionEncryptText;
+ private View mActionEncryptTextText;
private View mActionCertify;
private View mActionCertifyText;
private ImageView mActionCertifyImage;
@@ -85,6 +86,7 @@ public class ViewKeyMainFragment extends LoaderFragment implements
mActionEdit = view.findViewById(R.id.view_key_action_edit);
mActionEditDivider = view.findViewById(R.id.view_key_action_edit_divider);
mActionEncryptText = view.findViewById(R.id.view_key_action_encrypt_text);
+ mActionEncryptTextText = view.findViewById(R.id.view_key_action_encrypt_text_text);
mActionEncryptFiles = view.findViewById(R.id.view_key_action_encrypt_files);
mActionCertify = view.findViewById(R.id.view_key_action_certify);
mActionCertifyText = view.findViewById(R.id.view_key_action_certify_text);
@@ -235,6 +237,8 @@ public class ViewKeyMainFragment extends LoaderFragment implements
mActionEdit.setEnabled(false);
mActionCertify.setEnabled(false);
mActionCertifyText.setEnabled(false);
+ mActionEncryptText.setEnabled(false);
+ mActionEncryptTextText.setEnabled(false);
mActionEncryptFiles.setEnabled(false);
} else {
mActionEdit.setEnabled(true);
@@ -243,10 +247,14 @@ public class ViewKeyMainFragment extends LoaderFragment implements
if (!data.isNull(INDEX_UNIFIED_EXPIRY) && expiryDate.before(new Date())) {
mActionCertify.setEnabled(false);
mActionCertifyText.setEnabled(false);
+ mActionEncryptText.setEnabled(false);
+ mActionEncryptTextText.setEnabled(false);
mActionEncryptFiles.setEnabled(false);
} else {
mActionCertify.setEnabled(true);
mActionCertifyText.setEnabled(true);
+ mActionEncryptText.setEnabled(true);
+ mActionEncryptTextText.setEnabled(true);
mActionEncryptFiles.setEnabled(true);
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java
index f25c49508..38ba675e9 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java
@@ -22,8 +22,11 @@ import android.content.res.ColorStateList;
import android.database.Cursor;
import android.graphics.Typeface;
import android.support.v4.widget.CursorAdapter;
-import android.text.Html;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
import android.text.format.DateFormat;
+import android.text.style.StyleSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -143,29 +146,37 @@ public class SubkeysAdapter extends CursorAdapter {
long keyId = cursor.getLong(INDEX_KEY_ID);
String keyIdStr = PgpKeyHelper.convertKeyIdToHex(keyId);
+ vKeyId.setText(keyIdStr);
// may be set with additional "stripped" later on
- String algorithmStr = PgpKeyHelper.getAlgorithmInfo(
+ SpannableStringBuilder algorithmStr = new SpannableStringBuilder();
+ algorithmStr.append(PgpKeyHelper.getAlgorithmInfo(
context,
cursor.getInt(INDEX_ALGORITHM),
cursor.getInt(INDEX_KEY_SIZE),
cursor.getString(INDEX_KEY_CURVE_OID)
- );
-
- vKeyId.setText(keyIdStr);
+ ));
if (mSaveKeyringParcel != null && mSaveKeyringParcel.mStripSubKeys.contains(keyId)) {
- algorithmStr += ", <b>" + context.getString(R.string.key_stripped) + "</b>";
+ algorithmStr.append(", ");
+ final SpannableString boldStripped = new SpannableString(
+ context.getString(R.string.key_stripped)
+ );
+ boldStripped.setSpan(new StyleSpan(Typeface.BOLD), 0, boldStripped.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ algorithmStr.append(boldStripped);
} else {
switch (SecretKeyType.fromNum(cursor.getInt(INDEX_HAS_SECRET))) {
case GNU_DUMMY:
- algorithmStr += ", " + context.getString(R.string.key_stripped);
+ algorithmStr.append(", ");
+ algorithmStr.append(context.getString(R.string.key_stripped));
break;
case DIVERT_TO_CARD:
- algorithmStr += ", " + context.getString(R.string.key_divert);
+ algorithmStr.append(", ");
+ algorithmStr.append(context.getString(R.string.key_divert));
break;
case PASSPHRASE_EMPTY:
- algorithmStr += ", " + context.getString(R.string.key_no_passphrase);
+ algorithmStr.append(", ");
+ algorithmStr.append(context.getString(R.string.key_no_passphrase));
break;
case UNAVAILABLE:
// don't show this on pub keys
@@ -173,7 +184,7 @@ public class SubkeysAdapter extends CursorAdapter {
break;
}
}
- vKeyDetails.setText(Html.fromHtml(algorithmStr));
+ vKeyDetails.setText(algorithmStr, TextView.BufferType.SPANNABLE);
boolean isMasterKey = cursor.getInt(INDEX_RANK) == 0;
if (isMasterKey) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ListAwareSwipeRefreshLayout.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ListAwareSwipeRefreshLayout.java
new file mode 100644
index 000000000..58e8e81e9
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ListAwareSwipeRefreshLayout.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2014 Daniel Albert
+ *
+ * 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.widget;
+
+import android.content.Context;
+import android.support.v4.widget.SwipeRefreshLayout;
+import android.util.AttributeSet;
+
+import org.sufficientlysecure.keychain.util.Log;
+
+import se.emilsjolander.stickylistheaders.StickyListHeadersListView;
+
+public class ListAwareSwipeRefreshLayout extends SwipeRefreshLayout {
+
+
+ private StickyListHeadersListView mStickyListHeadersListView = null;
+ private boolean mIsLocked = false;
+
+ /**
+ * Constructors
+ */
+ public ListAwareSwipeRefreshLayout(Context context) {
+ super(context);
+ }
+ public ListAwareSwipeRefreshLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ /**
+ * Getters / Setters
+ */
+ public void setStickyListHeadersListView(StickyListHeadersListView stickyListHeadersListView) {
+ mStickyListHeadersListView = stickyListHeadersListView;
+ }
+ public StickyListHeadersListView getStickyListHeadersListView() {
+ return mStickyListHeadersListView;
+ }
+
+ public void setIsLocked(boolean locked) {
+ mIsLocked = locked;
+ Log.d("ListAwareSwipeRefreshLayout", (mIsLocked ? "is locked" : "not locked"));
+ }
+ public boolean getIsLocked() {
+ return mIsLocked;
+ }
+
+ @Override
+ public boolean canChildScrollUp() {
+ if (mStickyListHeadersListView == null)
+ return super.canChildScrollUp();
+
+ return (
+ mIsLocked
+ ||
+ (
+ mStickyListHeadersListView.getWrappedList().getChildCount() > 0
+ &&
+ (
+ mStickyListHeadersListView.getTop() > 0
+ ||
+ mStickyListHeadersListView.getFirstVisiblePosition() > 0
+ )
+ )
+ );
+ }
+} \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/key_list_fragment.xml b/OpenKeychain/src/main/res/layout/key_list_fragment.xml
index f1da19b72..6af40106d 100644
--- a/OpenKeychain/src/main/res/layout/key_list_fragment.xml
+++ b/OpenKeychain/src/main/res/layout/key_list_fragment.xml
@@ -1,82 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<org.sufficientlysecure.keychain.ui.widget.ListAwareSwipeRefreshLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/key_list_swipe_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
-
<!--rebuild functionality of ListFragment -->
-
- <se.emilsjolander.stickylistheaders.StickyListHeadersListView
- android:id="@+id/key_list_list"
+ <FrameLayout
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:clipToPadding="false"
- android:drawSelectorOnTop="true"
- android:fastScrollEnabled="true"
- android:paddingBottom="16dp"
- android:paddingLeft="16dp"
- android:paddingRight="32dp"
- android:scrollbarStyle="outsideOverlay" />
-
- <LinearLayout
- android:id="@+id/key_list_empty"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:gravity="center"
- android:orientation="vertical"
- android:visibility="visible">
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:text="@string/key_list_empty_text1"
- android:textAppearance="?android:attr/textAppearanceLarge" />
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:text=""
- android:textAppearance="?android:attr/textAppearanceLarge" />
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_margin="4dp"
- android:gravity="center"
- android:text="@string/key_list_empty_text2"
- android:textAppearance="?android:attr/textAppearanceSmall" />
-
- <Button
- android:id="@+id/key_list_empty_button_create"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_margin="4dp"
- android:textSize="14sp"
- android:text="@string/key_list_empty_button_create"
- android:drawableLeft="@drawable/ic_action_new_account"
- android:drawablePadding="8dp"
- android:background="@drawable/button_edgy"/>
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_margin="4dp"
+ android:layout_height="match_parent">
+ <se.emilsjolander.stickylistheaders.StickyListHeadersListView
+ android:id="@+id/key_list_list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipToPadding="false"
+ android:drawSelectorOnTop="true"
+ android:fastScrollEnabled="true"
+ android:paddingBottom="16dp"
+ android:paddingLeft="16dp"
+ android:paddingRight="32dp"
+ android:scrollbarStyle="outsideOverlay" />
+ <LinearLayout
+ android:id="@+id/key_list_empty"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:gravity="center"
- android:text="@string/key_list_empty_text3"
- android:textAppearance="?android:attr/textAppearanceSmall" />
-
- <Button
- android:id="@+id/key_list_empty_button_import"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_margin="4dp"
- android:textSize="14sp"
- android:text="@string/key_list_empty_button_import"
- android:drawableLeft="@drawable/ic_action_collection"
- android:drawablePadding="8dp"
- android:background="@drawable/button_edgy" />
- </LinearLayout>
-
-</FrameLayout>
+ android:orientation="vertical"
+ android:visibility="visible">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:text="@string/key_list_empty_text1"
+ android:textAppearance="?android:attr/textAppearanceLarge" />
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:text=""
+ android:textAppearance="?android:attr/textAppearanceLarge" />
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="4dp"
+ android:gravity="center"
+ android:text="@string/key_list_empty_text2"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+ <Button
+ android:id="@+id/key_list_empty_button_create"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="4dp"
+ android:textSize="14sp"
+ android:text="@string/key_list_empty_button_create"
+ android:drawableLeft="@drawable/ic_action_new_account"
+ android:drawablePadding="8dp"
+ android:background="@drawable/button_edgy"/>
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="4dp"
+ android:gravity="center"
+ android:text="@string/key_list_empty_text3"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+ <Button
+ android:id="@+id/key_list_empty_button_import"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="4dp"
+ android:textSize="14sp"
+ android:text="@string/key_list_empty_button_import"
+ android:drawableLeft="@drawable/ic_action_collection"
+ android:drawablePadding="8dp"
+ android:background="@drawable/button_edgy" />
+ </LinearLayout>
+ </FrameLayout>
+</org.sufficientlysecure.keychain.ui.widget.ListAwareSwipeRefreshLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/view_key_main_fragment.xml b/OpenKeychain/src/main/res/layout/view_key_main_fragment.xml
index 918e50dc4..6bcb216d7 100644
--- a/OpenKeychain/src/main/res/layout/view_key_main_fragment.xml
+++ b/OpenKeychain/src/main/res/layout/view_key_main_fragment.xml
@@ -103,6 +103,7 @@
android:orientation="horizontal">
<TextView
+ android:id="@+id/view_key_action_encrypt_text_text"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"