NON-WORKING Merge branch 'development' into linked-identities
Conflicts: OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedUserAttribute.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListActivity.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserAttributesAdapter.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/ContactHelper.java OpenKeychain/src/main/res/layout/view_key_main_fragment.xml OpenKeychain/src/main/res/values/strings.xml extern/spongycastle
This commit is contained in:
@@ -20,11 +20,6 @@ package org.sufficientlysecure.keychain;
|
||||
import android.os.Environment;
|
||||
|
||||
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.sufficientlysecure.keychain.remote.ui.AppsListActivity;
|
||||
import org.sufficientlysecure.keychain.ui.DecryptActivity;
|
||||
import org.sufficientlysecure.keychain.ui.EncryptFilesActivity;
|
||||
import org.sufficientlysecure.keychain.ui.EncryptTextActivity;
|
||||
import org.sufficientlysecure.keychain.ui.KeyListActivity;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@@ -91,21 +86,6 @@ public final class Constants {
|
||||
public static final int PREF_VERSION = 4;
|
||||
}
|
||||
|
||||
public static final class DrawerItems {
|
||||
public static final Class KEY_LIST = KeyListActivity.class;
|
||||
public static final Class ENCRYPT_TEXT = EncryptTextActivity.class;
|
||||
public static final Class ENCRYPT_FILE = EncryptFilesActivity.class;
|
||||
public static final Class DECRYPT = DecryptActivity.class;
|
||||
public static final Class REGISTERED_APPS_LIST = AppsListActivity.class;
|
||||
public static final Class[] ARRAY = new Class[]{
|
||||
KEY_LIST,
|
||||
ENCRYPT_TEXT,
|
||||
ENCRYPT_FILE,
|
||||
DECRYPT,
|
||||
REGISTERED_APPS_LIST
|
||||
};
|
||||
}
|
||||
|
||||
public static final class key {
|
||||
public static final int none = 0;
|
||||
public static final int symmetric = -1;
|
||||
|
||||
@@ -32,12 +32,12 @@ import android.provider.ContactsContract;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
import org.sufficientlysecure.keychain.util.TlsHelper;
|
||||
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
|
||||
import org.sufficientlysecure.keychain.ui.ConsolidateDialogActivity;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.PRNGFixes;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
import org.sufficientlysecure.keychain.util.TlsHelper;
|
||||
|
||||
import java.security.Security;
|
||||
|
||||
@@ -86,7 +86,7 @@ public class KeychainApplication extends Application {
|
||||
}
|
||||
|
||||
brandGlowEffect(getApplicationContext(),
|
||||
getApplicationContext().getResources().getColor(R.color.emphasis));
|
||||
getApplicationContext().getResources().getColor(R.color.primary));
|
||||
|
||||
setupAccountAsNeeded(this);
|
||||
|
||||
|
||||
+3
-3
@@ -17,8 +17,8 @@
|
||||
package org.sufficientlysecure.keychain.keyimport;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Vector;
|
||||
@@ -32,10 +32,10 @@ public class CloudSearch {
|
||||
|
||||
public static ArrayList<ImportKeysListEntry> search(final String query, Preferences.CloudSearchPrefs cloudPrefs)
|
||||
throws Keyserver.CloudSearchFailureException {
|
||||
final ArrayList<Keyserver> servers = new ArrayList<Keyserver>();
|
||||
final ArrayList<Keyserver> servers = new ArrayList<>();
|
||||
|
||||
// it's a Vector for sync, multiple threads might report problems
|
||||
final Vector<Keyserver.CloudSearchFailureException> problems = new Vector<Keyserver.CloudSearchFailureException>();
|
||||
final Vector<Keyserver.CloudSearchFailureException> problems = new Vector<>();
|
||||
|
||||
if (cloudPrefs.searchKeyserver) {
|
||||
servers.add(new HkpKeyserver(cloudPrefs.keyserver));
|
||||
|
||||
+4
-4
@@ -19,10 +19,10 @@
|
||||
package org.sufficientlysecure.keychain.keyimport;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.TlsHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.TlsHelper;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
@@ -234,7 +234,7 @@ public class HkpKeyserver extends Keyserver {
|
||||
@Override
|
||||
public ArrayList<ImportKeysListEntry> search(String query) throws QueryFailedException,
|
||||
QueryNeedsRepairException {
|
||||
ArrayList<ImportKeysListEntry> results = new ArrayList<ImportKeysListEntry>();
|
||||
ArrayList<ImportKeysListEntry> results = new ArrayList<>();
|
||||
|
||||
if (query.length() < 3) {
|
||||
throw new QueryTooShortException();
|
||||
@@ -305,7 +305,7 @@ public class HkpKeyserver extends Keyserver {
|
||||
entry.setRevoked(matcher.group(6).contains("r"));
|
||||
entry.setExpired(matcher.group(6).contains("e"));
|
||||
|
||||
ArrayList<String> userIds = new ArrayList<String>();
|
||||
ArrayList<String> userIds = new ArrayList<>();
|
||||
final String uidLines = matcher.group(7);
|
||||
final Matcher uidMatcher = UID_LINE.matcher(uidLines);
|
||||
while (uidMatcher.find()) {
|
||||
|
||||
+9
-9
@@ -23,9 +23,9 @@ import android.os.Parcelable;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
@@ -89,7 +89,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
||||
public ImportKeysListEntry createFromParcel(final Parcel source) {
|
||||
ImportKeysListEntry vr = new ImportKeysListEntry();
|
||||
vr.mPrimaryUserId = source.readString();
|
||||
vr.mUserIds = new ArrayList<String>();
|
||||
vr.mUserIds = new ArrayList<>();
|
||||
source.readStringList(vr.mUserIds);
|
||||
vr.mMergedUserIds = (HashMap<String, HashSet<String>>) source.readSerializable();
|
||||
vr.mKeyId = source.readLong();
|
||||
@@ -103,7 +103,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
||||
vr.mSecretKey = source.readByte() == 1;
|
||||
vr.mSelected = source.readByte() == 1;
|
||||
vr.mExtraData = source.readString();
|
||||
vr.mOrigins = new ArrayList<String>();
|
||||
vr.mOrigins = new ArrayList<>();
|
||||
source.readStringList(vr.mOrigins);
|
||||
|
||||
return vr;
|
||||
@@ -265,8 +265,8 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
||||
mSecretKey = false;
|
||||
// do not select by default
|
||||
mSelected = false;
|
||||
mUserIds = new ArrayList<String>();
|
||||
mOrigins = new ArrayList<String>();
|
||||
mUserIds = new ArrayList<>();
|
||||
mOrigins = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -294,8 +294,8 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
||||
mKeyId = key.getKeyId();
|
||||
mKeyIdHex = KeyFormattingUtils.convertKeyIdToHex(mKeyId);
|
||||
|
||||
mRevoked = key.isRevoked();
|
||||
mExpired = key.isExpired();
|
||||
mRevoked = key.isMaybeRevoked();
|
||||
mExpired = key.isMaybeExpired();
|
||||
mFingerprintHex = KeyFormattingUtils.convertFingerprintToHex(key.getFingerprint());
|
||||
mBitStrength = key.getBitStrength();
|
||||
mCurveOid = key.getCurveOid();
|
||||
@@ -304,7 +304,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
||||
}
|
||||
|
||||
public void updateMergedUserIds() {
|
||||
mMergedUserIds = new HashMap<String, HashSet<String>>();
|
||||
mMergedUserIds = new HashMap<>();
|
||||
for (String userId : mUserIds) {
|
||||
String[] userIdSplit = KeyRing.splitUserId(userId);
|
||||
|
||||
@@ -315,7 +315,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
||||
// email
|
||||
if (userIdSplit[1] != null) {
|
||||
if (!mMergedUserIds.containsKey(userIdSplit[0])) {
|
||||
HashSet<String> emails = new HashSet<String>();
|
||||
HashSet<String> emails = new HashSet<>();
|
||||
emails.add(userIdSplit[1]);
|
||||
mMergedUserIds.put(userIdSplit[0], emails);
|
||||
} else {
|
||||
|
||||
+2
-2
@@ -36,7 +36,7 @@ public class KeybaseKeyserver extends Keyserver {
|
||||
@Override
|
||||
public ArrayList<ImportKeysListEntry> search(String query) throws QueryFailedException,
|
||||
QueryNeedsRepairException {
|
||||
ArrayList<ImportKeysListEntry> results = new ArrayList<ImportKeysListEntry>();
|
||||
ArrayList<ImportKeysListEntry> results = new ArrayList<>();
|
||||
|
||||
if (query.startsWith("0x")) {
|
||||
// cut off "0x" if a user is searching for a key id
|
||||
@@ -81,7 +81,7 @@ public class KeybaseKeyserver extends Keyserver {
|
||||
final int algorithmId = match.getAlgorithmId();
|
||||
entry.setAlgorithm(KeyFormattingUtils.getAlgorithmInfo(algorithmId, bitStrength, null));
|
||||
|
||||
ArrayList<String> userIds = new ArrayList<String>();
|
||||
ArrayList<String> userIds = new ArrayList<>();
|
||||
String name = "<keybase.io/" + username + ">";
|
||||
if (fullName != null) {
|
||||
name = fullName + " " + name;
|
||||
|
||||
+6
@@ -77,6 +77,12 @@ public abstract class BaseOperation implements PassphraseCacheInterface {
|
||||
return mCancelled != null && mCancelled.get();
|
||||
}
|
||||
|
||||
protected void setPreventCancel () {
|
||||
if (mProgressable != null) {
|
||||
mProgressable.setPreventCancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCachedPassphrase(long subKeyId) throws NoSecretKeyException {
|
||||
try {
|
||||
|
||||
+5
-5
@@ -5,6 +5,10 @@ import android.content.Context;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
|
||||
import org.sufficientlysecure.keychain.keyimport.Keyserver.AddKeyException;
|
||||
import org.sufficientlysecure.keychain.operations.results.CertifyResult;
|
||||
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.pgp.CanonicalizedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
|
||||
@@ -16,10 +20,6 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
|
||||
import org.sufficientlysecure.keychain.service.CertifyActionsParcel;
|
||||
import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
|
||||
import org.sufficientlysecure.keychain.operations.results.CertifyResult;
|
||||
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.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
@@ -78,7 +78,7 @@ public class CertifyOperation extends BaseOperation {
|
||||
return new CertifyResult(CertifyResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
ArrayList<UncachedKeyRing> certifiedKeys = new ArrayList<UncachedKeyRing>();
|
||||
ArrayList<UncachedKeyRing> certifiedKeys = new ArrayList<>();
|
||||
|
||||
log.add(LogType.MSG_CRT_CERTIFYING, 1);
|
||||
|
||||
|
||||
+4
-5
@@ -4,17 +4,16 @@ import android.content.Context;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
|
||||
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.pgp.CanonicalizedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.Progressable;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
|
||||
import org.sufficientlysecure.keychain.service.CertifyActionsParcel;
|
||||
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.service.ContactSyncAdapterService;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
@@ -29,7 +28,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
* create key operations in PgpKeyOperation. It takes care of fetching
|
||||
* and saving the key before and after the operation.
|
||||
*
|
||||
* @see CertifyActionsParcel
|
||||
* @see SaveKeyringParcel
|
||||
*
|
||||
*/
|
||||
public class EditKeyOperation extends BaseOperation {
|
||||
|
||||
+50
-8
@@ -30,7 +30,12 @@ import org.sufficientlysecure.keychain.keyimport.KeybaseKeyserver;
|
||||
import org.sufficientlysecure.keychain.keyimport.Keyserver;
|
||||
import org.sufficientlysecure.keychain.keyimport.Keyserver.AddKeyException;
|
||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||
import org.sufficientlysecure.keychain.operations.results.ConsolidateResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.ExportResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
||||
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.pgp.CanonicalizedKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
@@ -40,13 +45,12 @@ 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.operations.results.OperationResult.LogType;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
|
||||
import org.sufficientlysecure.keychain.service.ContactSyncAdapterService;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.FileHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.ParcelableFileCache;
|
||||
import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize;
|
||||
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
@@ -58,6 +62,7 @@ import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/** An operation class which implements high level import and export
|
||||
@@ -123,6 +128,35 @@ public class ImportExportOperation extends BaseOperation {
|
||||
}
|
||||
}
|
||||
|
||||
public ImportKeyResult importKeyRings(List<ParcelableKeyRing> entries, String keyServerUri) {
|
||||
|
||||
Iterator<ParcelableKeyRing> it = entries.iterator();
|
||||
int numEntries = entries.size();
|
||||
|
||||
return importKeyRings(it, numEntries, keyServerUri);
|
||||
|
||||
}
|
||||
|
||||
public ImportKeyResult importKeyRings(ParcelableFileCache<ParcelableKeyRing> cache, String keyServerUri) {
|
||||
|
||||
// get entries from cached file
|
||||
try {
|
||||
IteratorWithSize<ParcelableKeyRing> it = cache.readCache();
|
||||
int numEntries = it.getSize();
|
||||
|
||||
return importKeyRings(it, numEntries, keyServerUri);
|
||||
} catch (IOException e) {
|
||||
|
||||
// Special treatment here, we need a lot
|
||||
OperationLog log = new OperationLog();
|
||||
log.add(LogType.MSG_IMPORT, 0, 0);
|
||||
log.add(LogType.MSG_IMPORT_ERROR_IO, 0, 0);
|
||||
|
||||
return new ImportKeyResult(ImportKeyResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public ImportKeyResult importKeyRings(Iterator<ParcelableKeyRing> entries, int num, String keyServerUri) {
|
||||
updateProgress(R.string.progress_importing, 0, 100);
|
||||
|
||||
@@ -131,13 +165,11 @@ public class ImportExportOperation extends BaseOperation {
|
||||
|
||||
// If there aren't even any keys, do nothing here.
|
||||
if (entries == null || !entries.hasNext()) {
|
||||
return new ImportKeyResult(
|
||||
ImportKeyResult.RESULT_FAIL_NOTHING, log, 0, 0, 0, 0,
|
||||
new long[]{});
|
||||
return new ImportKeyResult(ImportKeyResult.RESULT_FAIL_NOTHING, log);
|
||||
}
|
||||
|
||||
int newKeys = 0, oldKeys = 0, badKeys = 0, secret = 0;
|
||||
ArrayList<Long> importedMasterKeyIds = new ArrayList<Long>();
|
||||
ArrayList<Long> importedMasterKeyIds = new ArrayList<>();
|
||||
|
||||
boolean cancelled = false;
|
||||
int position = 0;
|
||||
@@ -293,6 +325,16 @@ public class ImportExportOperation extends BaseOperation {
|
||||
position++;
|
||||
}
|
||||
|
||||
// Special: consolidate on secret key import (cannot be cancelled!)
|
||||
if (secret > 0) {
|
||||
setPreventCancel();
|
||||
ConsolidateResult result = mProviderHelper.consolidateDatabaseStep1(mProgressable);
|
||||
log.add(result, 1);
|
||||
}
|
||||
|
||||
// Special: make sure new data is synced into contacts
|
||||
ContactSyncAdapterService.requestSync();
|
||||
|
||||
// convert to long array
|
||||
long[] importedMasterKeyIdsArray = new long[importedMasterKeyIds.size()];
|
||||
for (int i = 0; i < importedMasterKeyIds.size(); ++i) {
|
||||
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
package org.sufficientlysecure.keychain.operations;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
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.PromoteKeyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.Progressable;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/** An operation which promotes a public key ring to a secret one.
|
||||
*
|
||||
* This operation can only be applied to public key rings where no secret key
|
||||
* is available. Doing this "promotes" the public key ring to a secret one
|
||||
* without secret key material, using a GNU_DUMMY s2k type.
|
||||
*
|
||||
*/
|
||||
public class PromoteKeyOperation extends BaseOperation {
|
||||
|
||||
public PromoteKeyOperation(Context context, ProviderHelper providerHelper,
|
||||
Progressable progressable, AtomicBoolean cancelled) {
|
||||
super(context, providerHelper, progressable, cancelled);
|
||||
}
|
||||
|
||||
public PromoteKeyResult execute(long masterKeyId) {
|
||||
|
||||
OperationLog log = new OperationLog();
|
||||
log.add(LogType.MSG_PR, 0);
|
||||
|
||||
// Perform actual type change
|
||||
UncachedKeyRing promotedRing;
|
||||
{
|
||||
|
||||
try {
|
||||
|
||||
// This operation is only allowed for pure public keys
|
||||
// TODO delete secret keys if they are stripped, or have been moved to the card?
|
||||
if (mProviderHelper.getCachedPublicKeyRing(masterKeyId).hasAnySecret()) {
|
||||
log.add(LogType.MSG_PR_ERROR_ALREADY_SECRET, 2);
|
||||
return new PromoteKeyResult(PromoteKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
log.add(LogType.MSG_PR_FETCHING, 1,
|
||||
KeyFormattingUtils.convertKeyIdToHex(masterKeyId));
|
||||
CanonicalizedPublicKeyRing pubRing =
|
||||
mProviderHelper.getCanonicalizedPublicKeyRing(masterKeyId);
|
||||
|
||||
// create divert-to-card secret key from public key
|
||||
promotedRing = pubRing.createDummySecretRing();
|
||||
|
||||
} catch (PgpKeyNotFoundException e) {
|
||||
log.add(LogType.MSG_PR_ERROR_KEY_NOT_FOUND, 2);
|
||||
return new PromoteKeyResult(PromoteKeyResult.RESULT_ERROR, log, null);
|
||||
} catch (NotFoundException e) {
|
||||
log.add(LogType.MSG_PR_ERROR_KEY_NOT_FOUND, 2);
|
||||
return new PromoteKeyResult(PromoteKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
}
|
||||
|
||||
// If the edit operation didn't succeed, exit here
|
||||
if (promotedRing == null) {
|
||||
// error is already logged by modification
|
||||
return new PromoteKeyResult(PromoteKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
// Check if the action was cancelled
|
||||
if (checkCancelled()) {
|
||||
log.add(LogType.MSG_OPERATION_CANCELLED, 0);
|
||||
return new PromoteKeyResult(PgpEditKeyResult.RESULT_CANCELLED, log, null);
|
||||
}
|
||||
|
||||
// Cannot cancel from here on out!
|
||||
setPreventCancel();
|
||||
|
||||
// Save the new keyring.
|
||||
SaveKeyringResult saveResult = mProviderHelper
|
||||
.saveSecretKeyRing(promotedRing, new ProgressScaler(mProgressable, 60, 95, 100));
|
||||
log.add(saveResult, 1);
|
||||
|
||||
// If the save operation didn't succeed, exit here
|
||||
if (!saveResult.success()) {
|
||||
return new PromoteKeyResult(PromoteKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
updateProgress(R.string.progress_done, 100, 100);
|
||||
|
||||
log.add(LogType.MSG_PR_SUCCESS, 0);
|
||||
return new PromoteKeyResult(PromoteKeyResult.RESULT_OK, log, promotedRing.getMasterKeyId());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
package org.sufficientlysecure.keychain.operations;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.Progressable;
|
||||
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.util.FileHelper;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
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
|
||||
* 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 {
|
||||
|
||||
public SignEncryptOperation(Context context, ProviderHelper providerHelper,
|
||||
Progressable progressable, AtomicBoolean cancelled) {
|
||||
super(context, providerHelper, progressable, cancelled);
|
||||
}
|
||||
|
||||
public SignEncryptResult execute(SignEncryptParcel input) {
|
||||
|
||||
OperationLog log = new OperationLog();
|
||||
log.add(LogType.MSG_SE, 0);
|
||||
|
||||
ArrayDeque<Uri> inputUris = new ArrayDeque<>(input.getInputUris());
|
||||
ArrayDeque<Uri> outputUris = new ArrayDeque<>(input.getOutputUris());
|
||||
byte[] inputBytes = input.getBytes();
|
||||
byte[] outputBytes = null;
|
||||
|
||||
ArrayList<PgpSignEncryptResult> results = new ArrayList<>();
|
||||
|
||||
do {
|
||||
|
||||
if (checkCancelled()) {
|
||||
log.add(LogType.MSG_OPERATION_CANCELLED, 0);
|
||||
return new SignEncryptResult(SignEncryptResult.RESULT_CANCELLED, log, results);
|
||||
}
|
||||
|
||||
InputData inputData;
|
||||
{
|
||||
if (inputBytes != null) {
|
||||
log.add(LogType.MSG_SE_INPUT_BYTES, 1);
|
||||
InputStream is = new ByteArrayInputStream(inputBytes);
|
||||
inputData = new InputData(is, inputBytes.length);
|
||||
inputBytes = null;
|
||||
} else {
|
||||
if (inputUris.isEmpty()) {
|
||||
log.add(LogType.MSG_SE_ERROR_NO_INPUT, 1);
|
||||
return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log, results);
|
||||
}
|
||||
|
||||
log.add(LogType.MSG_SE_INPUT_URI, 1);
|
||||
Uri uri = inputUris.removeFirst();
|
||||
try {
|
||||
InputStream is = mContext.getContentResolver().openInputStream(uri);
|
||||
long fileSize = FileHelper.getFileSize(mContext, uri, 0);
|
||||
String filename = FileHelper.getFilename(mContext, uri);
|
||||
inputData = new InputData(is, fileSize, filename);
|
||||
} catch (FileNotFoundException e) {
|
||||
log.add(LogType.MSG_SE_ERROR_INPUT_URI_NOT_FOUND, 1);
|
||||
return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log, results);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OutputStream outStream;
|
||||
{
|
||||
if (!outputUris.isEmpty()) {
|
||||
try {
|
||||
Uri outputUri = outputUris.removeFirst();
|
||||
outStream = mContext.getContentResolver().openOutputStream(outputUri);
|
||||
} catch (FileNotFoundException e) {
|
||||
log.add(LogType.MSG_SE_ERROR_OUTPUT_URI_NOT_FOUND, 1);
|
||||
return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log, results);
|
||||
}
|
||||
} else {
|
||||
if (outputBytes != null) {
|
||||
log.add(LogType.MSG_SE_ERROR_TOO_MANY_INPUTS, 1);
|
||||
return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log, results);
|
||||
}
|
||||
outStream = new ByteArrayOutputStream();
|
||||
}
|
||||
}
|
||||
|
||||
PgpSignEncryptOperation op = new PgpSignEncryptOperation(mContext, mProviderHelper,
|
||||
new ProgressScaler(), mCancelled);
|
||||
PgpSignEncryptResult result = op.execute(input, inputData, outStream);
|
||||
results.add(result);
|
||||
log.add(result, 2);
|
||||
|
||||
if (result.isPending()) {
|
||||
return new SignEncryptResult(SignEncryptResult.RESULT_PENDING, log, results);
|
||||
}
|
||||
|
||||
if (!result.success()) {
|
||||
return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log, results);
|
||||
}
|
||||
|
||||
if (outStream instanceof ByteArrayOutputStream) {
|
||||
outputBytes = ((ByteArrayOutputStream) outStream).toByteArray();
|
||||
}
|
||||
|
||||
} while (!inputUris.isEmpty());
|
||||
|
||||
if (!outputUris.isEmpty()) {
|
||||
// Any output URIs left are indicative of a programming error
|
||||
log.add(LogType.MSG_SE_WARN_OUTPUT_LEFT, 1);
|
||||
}
|
||||
|
||||
log.add(LogType.MSG_SE_SUCCESS, 1);
|
||||
return new SignEncryptResult(SignEncryptResult.RESULT_OK, log, results, outputBytes);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+22
-44
@@ -21,18 +21,14 @@ package org.sufficientlysecure.keychain.operations.results;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.view.View;
|
||||
|
||||
import com.github.johnpersano.supertoasts.SuperCardToast;
|
||||
import com.github.johnpersano.supertoasts.SuperToast;
|
||||
import com.github.johnpersano.supertoasts.SuperToast.Duration;
|
||||
import com.github.johnpersano.supertoasts.util.OnClickWrapper;
|
||||
import com.github.johnpersano.supertoasts.util.Style;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.LogDisplayActivity;
|
||||
import org.sufficientlysecure.keychain.ui.LogDisplayFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Showable;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
|
||||
public class CertifyResult extends OperationResult {
|
||||
|
||||
@@ -78,30 +74,31 @@ public class CertifyResult extends OperationResult {
|
||||
}
|
||||
};
|
||||
|
||||
public SuperCardToast createNotify(final Activity activity) {
|
||||
public Showable createNotify(final Activity activity) {
|
||||
|
||||
int resultType = getResult();
|
||||
|
||||
String str;
|
||||
int duration, color;
|
||||
int duration;
|
||||
Style style;
|
||||
|
||||
// Not an overall failure
|
||||
if ((resultType & OperationResult.RESULT_ERROR) == 0) {
|
||||
String withWarnings;
|
||||
|
||||
duration = Duration.EXTRA_LONG;
|
||||
color = Style.GREEN;
|
||||
duration = Notify.LENGTH_LONG;
|
||||
style = Style.OK;
|
||||
withWarnings = "";
|
||||
|
||||
// Any warnings?
|
||||
if ((resultType & ImportKeyResult.RESULT_WARNINGS) > 0) {
|
||||
duration = 0;
|
||||
color = Style.ORANGE;
|
||||
style = Style.WARN;
|
||||
withWarnings += activity.getString(R.string.with_warnings);
|
||||
}
|
||||
if ((resultType & ImportKeyResult.RESULT_CANCELLED) > 0) {
|
||||
duration = 0;
|
||||
color = Style.ORANGE;
|
||||
style = Style.WARN;
|
||||
withWarnings += activity.getString(R.string.with_cancelled);
|
||||
}
|
||||
|
||||
@@ -111,46 +108,27 @@ public class CertifyResult extends OperationResult {
|
||||
if (mCertifyError > 0) {
|
||||
// definitely switch to warning-style message in this case!
|
||||
duration = 0;
|
||||
color = Style.RED;
|
||||
style = Style.ERROR;
|
||||
str += " " + activity.getResources().getQuantityString(
|
||||
R.plurals.certify_keys_with_errors, mCertifyError, mCertifyError);
|
||||
}
|
||||
|
||||
} else {
|
||||
duration = 0;
|
||||
color = Style.RED;
|
||||
style = Style.ERROR;
|
||||
str = activity.getResources().getQuantityString(R.plurals.certify_error,
|
||||
mCertifyError, mCertifyError);
|
||||
}
|
||||
|
||||
boolean button = getLog() != null && !getLog().isEmpty();
|
||||
SuperCardToast toast = new SuperCardToast(activity,
|
||||
button ? SuperToast.Type.BUTTON : SuperToast.Type.STANDARD,
|
||||
Style.getStyle(color, SuperToast.Animations.POPUP));
|
||||
toast.setText(str);
|
||||
toast.setDuration(duration);
|
||||
toast.setIndeterminate(duration == 0);
|
||||
toast.setSwipeToDismiss(true);
|
||||
// If we have a log and it's non-empty, show a View Log button
|
||||
if (button) {
|
||||
toast.setButtonIcon(R.drawable.ic_action_view_as_list,
|
||||
activity.getResources().getString(R.string.view_log));
|
||||
toast.setButtonTextColor(activity.getResources().getColor(R.color.black));
|
||||
toast.setTextColor(activity.getResources().getColor(R.color.black));
|
||||
toast.setOnClickWrapper(new OnClickWrapper("supercardtoast",
|
||||
new SuperToast.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view, Parcelable token) {
|
||||
Intent intent = new Intent(
|
||||
activity, LogDisplayActivity.class);
|
||||
intent.putExtra(LogDisplayFragment.EXTRA_RESULT, CertifyResult.this);
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
return toast;
|
||||
return Notify.createNotify(activity, str, duration, style, new ActionListener() {
|
||||
@Override
|
||||
public void onAction() {
|
||||
Intent intent = new Intent(
|
||||
activity, LogDisplayActivity.class);
|
||||
intent.putExtra(LogDisplayFragment.EXTRA_RESULT, CertifyResult.this);
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
}, R.string.view_log);
|
||||
|
||||
}
|
||||
|
||||
|
||||
+11
@@ -41,6 +41,9 @@ public class DecryptVerifyResult extends OperationResult {
|
||||
|
||||
OpenPgpSignatureResult mSignatureResult;
|
||||
OpenPgpMetadata mDecryptMetadata;
|
||||
// This holds the charset which was specified in the ascii armor, if specified
|
||||
// https://tools.ietf.org/html/rfc4880#page56
|
||||
String mCharset;
|
||||
|
||||
public long getKeyIdPassphraseNeeded() {
|
||||
return mKeyIdPassphraseNeeded;
|
||||
@@ -84,6 +87,14 @@ public class DecryptVerifyResult extends OperationResult {
|
||||
mDecryptMetadata = decryptMetadata;
|
||||
}
|
||||
|
||||
public String getCharset () {
|
||||
return mCharset;
|
||||
}
|
||||
|
||||
public void setCharset(String charset) {
|
||||
mCharset = charset;
|
||||
}
|
||||
|
||||
public boolean isPending() {
|
||||
return (mResult & RESULT_PENDING) == RESULT_PENDING;
|
||||
}
|
||||
|
||||
+22
-44
@@ -21,18 +21,14 @@ package org.sufficientlysecure.keychain.operations.results;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.view.View;
|
||||
|
||||
import com.github.johnpersano.supertoasts.SuperCardToast;
|
||||
import com.github.johnpersano.supertoasts.SuperToast;
|
||||
import com.github.johnpersano.supertoasts.SuperToast.Duration;
|
||||
import com.github.johnpersano.supertoasts.util.OnClickWrapper;
|
||||
import com.github.johnpersano.supertoasts.util.Style;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.LogDisplayActivity;
|
||||
import org.sufficientlysecure.keychain.ui.LogDisplayFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Showable;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
|
||||
public class DeleteResult extends OperationResult {
|
||||
|
||||
@@ -68,31 +64,32 @@ public class DeleteResult extends OperationResult {
|
||||
}
|
||||
};
|
||||
|
||||
public SuperCardToast createNotify(final Activity activity) {
|
||||
public Showable createNotify(final Activity activity) {
|
||||
|
||||
int resultType = getResult();
|
||||
|
||||
String str;
|
||||
int duration, color;
|
||||
int duration;
|
||||
Style style;
|
||||
|
||||
// Not an overall failure
|
||||
if ((resultType & OperationResult.RESULT_ERROR) == 0) {
|
||||
String untilCancelled;
|
||||
|
||||
duration = Duration.EXTRA_LONG;
|
||||
color = Style.GREEN;
|
||||
duration = Notify.LENGTH_LONG;
|
||||
style = Style.OK;
|
||||
untilCancelled = "";
|
||||
|
||||
// Any warnings?
|
||||
if ((resultType & ImportKeyResult.RESULT_CANCELLED) > 0) {
|
||||
duration = 0;
|
||||
color = Style.ORANGE;
|
||||
style = Style.WARN;
|
||||
untilCancelled += activity.getString(R.string.with_cancelled);
|
||||
}
|
||||
|
||||
// New and updated keys
|
||||
if (mOk > 0 && mFail > 0) {
|
||||
color = Style.ORANGE;
|
||||
style = Style.WARN;
|
||||
duration = 0;
|
||||
str = activity.getResources().getQuantityString(
|
||||
R.plurals.delete_ok_but_fail_1, mOk, mOk);
|
||||
@@ -105,13 +102,13 @@ public class DeleteResult extends OperationResult {
|
||||
str = activity.getString(R.string.delete_cancelled);
|
||||
} else {
|
||||
duration = 0;
|
||||
color = Style.RED;
|
||||
style = Style.ERROR;
|
||||
str = "internal error";
|
||||
}
|
||||
|
||||
} else {
|
||||
duration = 0;
|
||||
color = Style.RED;
|
||||
style = Style.ERROR;
|
||||
if (mFail == 0) {
|
||||
str = activity.getString(R.string.delete_nothing);
|
||||
} else {
|
||||
@@ -119,34 +116,15 @@ public class DeleteResult extends OperationResult {
|
||||
}
|
||||
}
|
||||
|
||||
boolean button = getLog() != null && !getLog().isEmpty();
|
||||
SuperCardToast toast = new SuperCardToast(activity,
|
||||
button ? SuperToast.Type.BUTTON : SuperToast.Type.STANDARD,
|
||||
Style.getStyle(color, SuperToast.Animations.POPUP));
|
||||
toast.setText(str);
|
||||
toast.setDuration(duration);
|
||||
toast.setIndeterminate(duration == 0);
|
||||
toast.setSwipeToDismiss(true);
|
||||
// If we have a log and it's non-empty, show a View Log button
|
||||
if (button) {
|
||||
toast.setButtonIcon(R.drawable.ic_action_view_as_list,
|
||||
activity.getResources().getString(R.string.view_log));
|
||||
toast.setButtonTextColor(activity.getResources().getColor(R.color.black));
|
||||
toast.setTextColor(activity.getResources().getColor(R.color.black));
|
||||
toast.setOnClickWrapper(new OnClickWrapper("supercardtoast",
|
||||
new SuperToast.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view, Parcelable token) {
|
||||
Intent intent = new Intent(
|
||||
activity, LogDisplayActivity.class);
|
||||
intent.putExtra(LogDisplayFragment.EXTRA_RESULT, DeleteResult.this);
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
return toast;
|
||||
return Notify.createNotify(activity, str, duration, style, new ActionListener() {
|
||||
@Override
|
||||
public void onAction() {
|
||||
Intent intent = new Intent(
|
||||
activity, LogDisplayActivity.class);
|
||||
intent.putExtra(LogDisplayFragment.EXTRA_RESULT, DeleteResult.this);
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
}, R.string.view_log);
|
||||
|
||||
}
|
||||
|
||||
|
||||
+27
-45
@@ -21,18 +21,14 @@ package org.sufficientlysecure.keychain.operations.results;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.view.View;
|
||||
|
||||
import com.github.johnpersano.supertoasts.SuperCardToast;
|
||||
import com.github.johnpersano.supertoasts.SuperToast;
|
||||
import com.github.johnpersano.supertoasts.SuperToast.Duration;
|
||||
import com.github.johnpersano.supertoasts.util.OnClickWrapper;
|
||||
import com.github.johnpersano.supertoasts.util.Style;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.LogDisplayActivity;
|
||||
import org.sufficientlysecure.keychain.ui.LogDisplayFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Showable;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
|
||||
public class ImportKeyResult extends OperationResult {
|
||||
|
||||
@@ -83,6 +79,10 @@ public class ImportKeyResult extends OperationResult {
|
||||
mImportedMasterKeyIds = source.createLongArray();
|
||||
}
|
||||
|
||||
public ImportKeyResult(int result, OperationLog log) {
|
||||
this(result, log, 0, 0, 0, 0, new long[] { });
|
||||
}
|
||||
|
||||
public ImportKeyResult(int result, OperationLog log,
|
||||
int newKeys, int updatedKeys, int badKeys, int secret,
|
||||
long[] importedMasterKeyIds) {
|
||||
@@ -114,30 +114,31 @@ public class ImportKeyResult extends OperationResult {
|
||||
}
|
||||
};
|
||||
|
||||
public SuperCardToast createNotify(final Activity activity) {
|
||||
public Showable createNotify(final Activity activity) {
|
||||
|
||||
int resultType = getResult();
|
||||
|
||||
String str;
|
||||
int duration, color;
|
||||
int duration;
|
||||
Style style;
|
||||
|
||||
// Not an overall failure
|
||||
if ((resultType & OperationResult.RESULT_ERROR) == 0) {
|
||||
String withWarnings;
|
||||
|
||||
duration = Duration.EXTRA_LONG;
|
||||
color = Style.GREEN;
|
||||
duration = Notify.LENGTH_LONG;
|
||||
style = Style.OK;
|
||||
withWarnings = "";
|
||||
|
||||
// Any warnings?
|
||||
if ((resultType & ImportKeyResult.RESULT_WARNINGS) > 0) {
|
||||
duration = 0;
|
||||
color = Style.ORANGE;
|
||||
style = Style.WARN;
|
||||
withWarnings += activity.getString(R.string.with_warnings);
|
||||
}
|
||||
if ((resultType & ImportKeyResult.RESULT_CANCELLED) > 0) {
|
||||
duration = 0;
|
||||
color = Style.ORANGE;
|
||||
style = Style.WARN;
|
||||
withWarnings += activity.getString(R.string.with_cancelled);
|
||||
}
|
||||
|
||||
@@ -155,20 +156,20 @@ public class ImportKeyResult extends OperationResult {
|
||||
R.plurals.import_keys_added, mNewKeys, mNewKeys, withWarnings);
|
||||
} else {
|
||||
duration = 0;
|
||||
color = Style.RED;
|
||||
style = Style.ERROR;
|
||||
str = "internal error";
|
||||
}
|
||||
if (isOkWithErrors()) {
|
||||
// definitely switch to warning-style message in this case!
|
||||
duration = 0;
|
||||
color = Style.RED;
|
||||
style = Style.ERROR;
|
||||
str += " " + activity.getResources().getQuantityString(
|
||||
R.plurals.import_keys_with_errors, mBadKeys, mBadKeys);
|
||||
}
|
||||
|
||||
} else {
|
||||
duration = 0;
|
||||
color = Style.RED;
|
||||
style = Style.ERROR;
|
||||
if (isFailNothing()) {
|
||||
str = activity.getString((resultType & ImportKeyResult.RESULT_CANCELLED) > 0
|
||||
? R.string.import_error_nothing_cancelled
|
||||
@@ -178,34 +179,15 @@ public class ImportKeyResult extends OperationResult {
|
||||
}
|
||||
}
|
||||
|
||||
boolean button = getLog() != null && !getLog().isEmpty();
|
||||
SuperCardToast toast = new SuperCardToast(activity,
|
||||
button ? SuperToast.Type.BUTTON : SuperToast.Type.STANDARD,
|
||||
Style.getStyle(color, SuperToast.Animations.POPUP));
|
||||
toast.setText(str);
|
||||
toast.setDuration(duration);
|
||||
toast.setIndeterminate(duration == 0);
|
||||
toast.setSwipeToDismiss(true);
|
||||
// If we have a log and it's non-empty, show a View Log button
|
||||
if (button) {
|
||||
toast.setButtonIcon(R.drawable.ic_action_view_as_list,
|
||||
activity.getResources().getString(R.string.view_log));
|
||||
toast.setButtonTextColor(activity.getResources().getColor(R.color.black));
|
||||
toast.setTextColor(activity.getResources().getColor(R.color.black));
|
||||
toast.setOnClickWrapper(new OnClickWrapper("supercardtoast",
|
||||
new SuperToast.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view, Parcelable token) {
|
||||
Intent intent = new Intent(
|
||||
activity, LogDisplayActivity.class);
|
||||
intent.putExtra(LogDisplayFragment.EXTRA_RESULT, ImportKeyResult.this);
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
return toast;
|
||||
return Notify.createNotify(activity, str, duration, style, new ActionListener() {
|
||||
@Override
|
||||
public void onAction() {
|
||||
Intent intent = new Intent(
|
||||
activity, LogDisplayActivity.class);
|
||||
intent.putExtra(LogDisplayFragment.EXTRA_RESULT, ImportKeyResult.this);
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
}, R.string.view_log);
|
||||
|
||||
}
|
||||
|
||||
|
||||
+85
-65
@@ -22,17 +22,15 @@ import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.view.View;
|
||||
|
||||
import com.github.johnpersano.supertoasts.SuperCardToast;
|
||||
import com.github.johnpersano.supertoasts.SuperToast;
|
||||
import com.github.johnpersano.supertoasts.util.OnClickWrapper;
|
||||
import com.github.johnpersano.supertoasts.util.Style;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.LogDisplayActivity;
|
||||
import org.sufficientlysecure.keychain.ui.LogDisplayFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Showable;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
@@ -195,58 +193,44 @@ public abstract class OperationResult implements Parcelable {
|
||||
|
||||
}
|
||||
|
||||
public SuperCardToast createNotify(final Activity activity) {
|
||||
|
||||
int color;
|
||||
public Showable createNotify(final Activity activity) {
|
||||
|
||||
Log.d(Constants.TAG, "mLog.getLast()"+mLog.getLast());
|
||||
Log.d(Constants.TAG, "mLog.getLast().mType"+mLog.getLast().mType);
|
||||
Log.d(Constants.TAG, "mLog.getLast().mType.getMsgId()"+mLog.getLast().mType.getMsgId());
|
||||
|
||||
// Take the last message as string
|
||||
String str = activity.getString(mLog.getLast().mType.getMsgId());
|
||||
int msgId = mLog.getLast().mType.getMsgId();
|
||||
|
||||
Style style;
|
||||
|
||||
// Not an overall failure
|
||||
if (cancelled()) {
|
||||
color = Style.RED;
|
||||
style = Style.ERROR;
|
||||
} else if (success()) {
|
||||
if (getLog().containsWarnings()) {
|
||||
color = Style.ORANGE;
|
||||
style = Style.WARN;
|
||||
} else {
|
||||
color = Style.GREEN;
|
||||
style = Style.OK;
|
||||
}
|
||||
} else {
|
||||
color = Style.RED;
|
||||
style = Style.ERROR;
|
||||
}
|
||||
|
||||
boolean button = getLog() != null && !getLog().isEmpty();
|
||||
SuperCardToast toast = new SuperCardToast(activity,
|
||||
button ? SuperToast.Type.BUTTON : SuperToast.Type.STANDARD,
|
||||
Style.getStyle(color, SuperToast.Animations.POPUP));
|
||||
toast.setText(str);
|
||||
toast.setDuration(SuperToast.Duration.EXTRA_LONG);
|
||||
toast.setIndeterminate(false);
|
||||
toast.setSwipeToDismiss(true);
|
||||
// If we have a log and it's non-empty, show a View Log button
|
||||
if (button) {
|
||||
toast.setButtonIcon(R.drawable.ic_action_view_as_list,
|
||||
activity.getResources().getString(R.string.view_log));
|
||||
toast.setButtonTextColor(activity.getResources().getColor(R.color.black));
|
||||
toast.setTextColor(activity.getResources().getColor(R.color.black));
|
||||
toast.setOnClickWrapper(new OnClickWrapper("supercardtoast",
|
||||
new SuperToast.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view, Parcelable token) {
|
||||
Intent intent = new Intent(
|
||||
activity, LogDisplayActivity.class);
|
||||
intent.putExtra(LogDisplayFragment.EXTRA_RESULT, OperationResult.this);
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
}
|
||||
));
|
||||
if (getLog() == null || getLog().isEmpty()) {
|
||||
return Notify.createNotify(activity, msgId, Notify.LENGTH_LONG, style);
|
||||
}
|
||||
|
||||
return toast;
|
||||
return Notify.createNotify(activity, msgId, Notify.LENGTH_LONG, style,
|
||||
new ActionListener() {
|
||||
@Override
|
||||
public void onAction() {
|
||||
Intent intent = new Intent(
|
||||
activity, LogDisplayActivity.class);
|
||||
intent.putExtra(LogDisplayFragment.EXTRA_RESULT, OperationResult.this);
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
}, R.string.view_log);
|
||||
|
||||
}
|
||||
|
||||
@@ -473,6 +457,7 @@ public abstract class OperationResult implements Parcelable {
|
||||
|
||||
// secret key modify
|
||||
MSG_MF (LogLevel.START, R.string.msg_mr),
|
||||
MSG_MF_ERROR_DIVERT_SERIAL (LogLevel.ERROR, R.string.msg_mf_error_divert_serial),
|
||||
MSG_MF_ERROR_ENCODE (LogLevel.ERROR, R.string.msg_mf_error_encode),
|
||||
MSG_MF_ERROR_FINGERPRINT (LogLevel.ERROR, R.string.msg_mf_error_fingerprint),
|
||||
MSG_MF_ERROR_KEYID (LogLevel.ERROR, R.string.msg_mf_error_keyid),
|
||||
@@ -485,6 +470,7 @@ public abstract class OperationResult implements Parcelable {
|
||||
MSG_MF_ERROR_PASSPHRASE_MASTER(LogLevel.ERROR, R.string.msg_mf_error_passphrase_master),
|
||||
MSG_MF_ERROR_PAST_EXPIRY(LogLevel.ERROR, R.string.msg_mf_error_past_expiry),
|
||||
MSG_MF_ERROR_PGP (LogLevel.ERROR, R.string.msg_mf_error_pgp),
|
||||
MSG_MF_ERROR_RESTRICTED(LogLevel.ERROR, R.string.msg_mf_error_restricted),
|
||||
MSG_MF_ERROR_REVOKED_PRIMARY (LogLevel.ERROR, R.string.msg_mf_error_revoked_primary),
|
||||
MSG_MF_ERROR_SIG (LogLevel.ERROR, R.string.msg_mf_error_sig),
|
||||
MSG_MF_ERROR_SUBKEY_MISSING(LogLevel.ERROR, R.string.msg_mf_error_subkey_missing),
|
||||
@@ -527,6 +513,7 @@ public abstract class OperationResult implements Parcelable {
|
||||
MSG_CON_ERROR_PUBLIC (LogLevel.ERROR, R.string.msg_con_error_public),
|
||||
MSG_CON_ERROR_SECRET (LogLevel.ERROR, R.string.msg_con_error_secret),
|
||||
MSG_CON_RECOVER (LogLevel.DEBUG, R.string.msg_con_recover),
|
||||
MSG_CON_RECURSIVE (LogLevel.OK, R.string.msg_con_recursive),
|
||||
MSG_CON_REIMPORT_PUBLIC (LogLevel.DEBUG, R.plurals.msg_con_reimport_public),
|
||||
MSG_CON_REIMPORT_PUBLIC_SKIP (LogLevel.DEBUG, R.string.msg_con_reimport_public_skip),
|
||||
MSG_CON_REIMPORT_SECRET (LogLevel.DEBUG, R.plurals.msg_con_reimport_secret),
|
||||
@@ -546,6 +533,13 @@ public abstract class OperationResult implements Parcelable {
|
||||
MSG_ED_FETCHING (LogLevel.DEBUG, R.string.msg_ed_fetching),
|
||||
MSG_ED_SUCCESS (LogLevel.OK, R.string.msg_ed_success),
|
||||
|
||||
// promote key
|
||||
MSG_PR (LogLevel.START, R.string.msg_pr),
|
||||
MSG_PR_ERROR_ALREADY_SECRET (LogLevel.ERROR, R.string.msg_pr_error_already_secret),
|
||||
MSG_PR_ERROR_KEY_NOT_FOUND (LogLevel.ERROR, R.string.msg_pr_error_key_not_found),
|
||||
MSG_PR_FETCHING (LogLevel.DEBUG, R.string.msg_pr_fetching),
|
||||
MSG_PR_SUCCESS (LogLevel.OK, R.string.msg_pr_success),
|
||||
|
||||
// messages used in UI code
|
||||
MSG_EK_ERROR_DIVERT (LogLevel.ERROR, R.string.msg_ek_error_divert),
|
||||
MSG_EK_ERROR_DUMMY (LogLevel.ERROR, R.string.msg_ek_error_dummy),
|
||||
@@ -555,11 +549,13 @@ public abstract class OperationResult implements Parcelable {
|
||||
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_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),
|
||||
MSG_DC_CLEAR_META_MIME (LogLevel.DEBUG, R.string.msg_dc_clear_meta_mime),
|
||||
MSG_DC_CLEAR_META_SIZE (LogLevel.DEBUG, R.string.msg_dc_clear_meta_size),
|
||||
MSG_DC_CLEAR_META_SIZE_UNKNOWN (LogLevel.DEBUG, R.string.msg_dc_clear_meta_size_unknown),
|
||||
MSG_DC_CLEAR_META_TIME (LogLevel.DEBUG, R.string.msg_dc_clear_meta_time),
|
||||
MSG_DC_CLEAR (LogLevel.DEBUG, R.string.msg_dc_clear),
|
||||
MSG_DC_CLEAR_SIGNATURE_BAD (LogLevel.WARN, R.string.msg_dc_clear_signature_bad),
|
||||
@@ -590,29 +586,52 @@ public abstract class OperationResult implements Parcelable {
|
||||
MSG_DC_TRAIL_UNKNOWN (LogLevel.DEBUG, R.string.msg_dc_trail_unknown),
|
||||
MSG_DC_UNLOCKING (LogLevel.INFO, R.string.msg_dc_unlocking),
|
||||
|
||||
// verify signed literal data
|
||||
MSG_VL (LogLevel.INFO, R.string.msg_vl),
|
||||
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_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),
|
||||
|
||||
// signencrypt
|
||||
MSG_SE_ASYMMETRIC (LogLevel.INFO, R.string.msg_se_asymmetric),
|
||||
MSG_SE_CLEARSIGN_ONLY (LogLevel.DEBUG, R.string.msg_se_clearsign_only),
|
||||
MSG_SE_COMPRESSING (LogLevel.DEBUG, R.string.msg_se_compressing),
|
||||
MSG_SE_ENCRYPTING (LogLevel.DEBUG, R.string.msg_se_encrypting),
|
||||
MSG_SE_ERROR_BAD_PASSPHRASE (LogLevel.ERROR, R.string.msg_se_error_bad_passphrase),
|
||||
MSG_SE_ERROR_IO (LogLevel.ERROR, R.string.msg_se_error_io),
|
||||
MSG_SE_ERROR_SIGN_KEY(LogLevel.ERROR, R.string.msg_se_error_sign_key),
|
||||
MSG_SE_ERROR_KEY_SIGN (LogLevel.ERROR, R.string.msg_se_error_key_sign),
|
||||
MSG_SE_ERROR_NFC (LogLevel.ERROR, R.string.msg_se_error_nfc),
|
||||
MSG_SE_ERROR_PGP (LogLevel.ERROR, R.string.msg_se_error_pgp),
|
||||
MSG_SE_ERROR_SIG (LogLevel.ERROR, R.string.msg_se_error_sig),
|
||||
MSG_SE_ERROR_UNLOCK (LogLevel.ERROR, R.string.msg_se_error_unlock),
|
||||
MSG_SE_KEY_OK (LogLevel.OK, R.string.msg_se_key_ok),
|
||||
MSG_SE_KEY_UNKNOWN (LogLevel.DEBUG, R.string.msg_se_key_unknown),
|
||||
MSG_SE_KEY_WARN (LogLevel.WARN, R.string.msg_se_key_warn),
|
||||
MSG_SE_OK (LogLevel.OK, R.string.msg_se_ok),
|
||||
MSG_SE_PENDING_NFC (LogLevel.INFO, R.string.msg_se_pending_nfc),
|
||||
MSG_SE_PENDING_PASSPHRASE (LogLevel.INFO, R.string.msg_se_pending_passphrase),
|
||||
MSG_SE (LogLevel.DEBUG, R.string.msg_se),
|
||||
MSG_SE_SIGNING (LogLevel.DEBUG, R.string.msg_se_signing),
|
||||
MSG_SE_SIGCRYPTING (LogLevel.DEBUG, R.string.msg_se_sigcrypting),
|
||||
MSG_SE_SYMMETRIC (LogLevel.INFO, R.string.msg_se_symmetric),
|
||||
MSG_SE (LogLevel.START, R.string.msg_se),
|
||||
MSG_SE_INPUT_BYTES (LogLevel.INFO, R.string.msg_se_input_bytes),
|
||||
MSG_SE_INPUT_URI (LogLevel.INFO, R.string.msg_se_input_uri),
|
||||
MSG_SE_ERROR_NO_INPUT (LogLevel.DEBUG, R.string.msg_se_error_no_input),
|
||||
MSG_SE_ERROR_INPUT_URI_NOT_FOUND (LogLevel.ERROR, R.string.msg_se_error_input_uri_not_found),
|
||||
MSG_SE_ERROR_OUTPUT_URI_NOT_FOUND (LogLevel.ERROR, R.string.msg_se_error_output_uri_not_found),
|
||||
MSG_SE_ERROR_TOO_MANY_INPUTS (LogLevel.ERROR, R.string.msg_se_error_too_many_inputs),
|
||||
MSG_SE_WARN_OUTPUT_LEFT (LogLevel.WARN, R.string.msg_se_warn_output_left),
|
||||
MSG_SE_SUCCESS (LogLevel.OK, R.string.msg_se_success),
|
||||
|
||||
// pgpsignencrypt
|
||||
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),
|
||||
MSG_PSE_ERROR_HASH_ALGO (LogLevel.ERROR, R.string.msg_pse_error_hash_algo),
|
||||
MSG_PSE_ERROR_IO (LogLevel.ERROR, R.string.msg_pse_error_io),
|
||||
MSG_PSE_ERROR_SIGN_KEY(LogLevel.ERROR, R.string.msg_pse_error_sign_key),
|
||||
MSG_PSE_ERROR_KEY_SIGN (LogLevel.ERROR, R.string.msg_pse_error_key_sign),
|
||||
MSG_PSE_ERROR_NFC (LogLevel.ERROR, R.string.msg_pse_error_nfc),
|
||||
MSG_PSE_ERROR_PGP (LogLevel.ERROR, R.string.msg_pse_error_pgp),
|
||||
MSG_PSE_ERROR_SIG (LogLevel.ERROR, R.string.msg_pse_error_sig),
|
||||
MSG_PSE_ERROR_UNLOCK (LogLevel.ERROR, R.string.msg_pse_error_unlock),
|
||||
MSG_PSE_KEY_OK (LogLevel.OK, R.string.msg_pse_key_ok),
|
||||
MSG_PSE_KEY_UNKNOWN (LogLevel.DEBUG, R.string.msg_pse_key_unknown),
|
||||
MSG_PSE_KEY_WARN (LogLevel.WARN, R.string.msg_pse_key_warn),
|
||||
MSG_PSE_OK (LogLevel.OK, R.string.msg_pse_ok),
|
||||
MSG_PSE_PENDING_NFC (LogLevel.INFO, R.string.msg_pse_pending_nfc),
|
||||
MSG_PSE_PENDING_PASSPHRASE (LogLevel.INFO, R.string.msg_pse_pending_passphrase),
|
||||
MSG_PSE (LogLevel.DEBUG, R.string.msg_pse),
|
||||
MSG_PSE_SIGNING (LogLevel.DEBUG, R.string.msg_pse_signing),
|
||||
MSG_PSE_SIGNING_CLEARTEXT (LogLevel.DEBUG, R.string.msg_pse_signing_cleartext),
|
||||
MSG_PSE_SIGNING_DETACHED (LogLevel.DEBUG, R.string.msg_pse_signing_detached),
|
||||
MSG_PSE_SIGCRYPTING (LogLevel.DEBUG, R.string.msg_pse_sigcrypting),
|
||||
MSG_PSE_SYMMETRIC (LogLevel.INFO, R.string.msg_pse_symmetric),
|
||||
|
||||
MSG_CRT_CERTIFYING (LogLevel.DEBUG, R.string.msg_crt_certifying),
|
||||
MSG_CRT_CERTIFY_ALL (LogLevel.DEBUG, R.string.msg_crt_certify_all),
|
||||
@@ -645,6 +664,7 @@ public abstract class OperationResult implements Parcelable {
|
||||
MSG_IMPORT_FINGERPRINT_ERROR (LogLevel.ERROR, R.string.msg_import_fingerprint_error),
|
||||
MSG_IMPORT_FINGERPRINT_OK (LogLevel.DEBUG, R.string.msg_import_fingerprint_ok),
|
||||
MSG_IMPORT_ERROR (LogLevel.ERROR, R.string.msg_import_error),
|
||||
MSG_IMPORT_ERROR_IO (LogLevel.ERROR, R.string.msg_import_error_io),
|
||||
MSG_IMPORT_PARTIAL (LogLevel.ERROR, R.string.msg_import_partial),
|
||||
MSG_IMPORT_SUCCESS (LogLevel.OK, R.string.msg_import_success),
|
||||
|
||||
@@ -742,7 +762,7 @@ public abstract class OperationResult implements Parcelable {
|
||||
|
||||
public static class OperationLog implements Iterable<LogEntryParcel> {
|
||||
|
||||
private final List<LogEntryParcel> mParcels = new ArrayList<LogEntryParcel>();
|
||||
private final List<LogEntryParcel> mParcels = new ArrayList<>();
|
||||
|
||||
/// Simple convenience method
|
||||
public void add(LogType type, int indent, Object... parameters) {
|
||||
@@ -767,7 +787,7 @@ public abstract class OperationResult implements Parcelable {
|
||||
}
|
||||
|
||||
public boolean containsType(LogType type) {
|
||||
for(LogEntryParcel entry : new IterableIterator<LogEntryParcel>(mParcels.iterator())) {
|
||||
for(LogEntryParcel entry : new IterableIterator<>(mParcels.iterator())) {
|
||||
if (entry.mType == type) {
|
||||
return true;
|
||||
}
|
||||
@@ -776,7 +796,7 @@ public abstract class OperationResult implements Parcelable {
|
||||
}
|
||||
|
||||
public boolean containsWarnings() {
|
||||
for(LogEntryParcel entry : new IterableIterator<LogEntryParcel>(mParcels.iterator())) {
|
||||
for(LogEntryParcel entry : new IterableIterator<>(mParcels.iterator())) {
|
||||
if (entry.mType.mLevel == LogLevel.WARN || entry.mType.mLevel == LogLevel.ERROR) {
|
||||
return true;
|
||||
}
|
||||
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* 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 java.util.Date;
|
||||
|
||||
public class PgpSignEncryptResult extends OperationResult {
|
||||
|
||||
// the fourth bit indicates a "data pending" result! (it's also a form of non-success)
|
||||
public static final int RESULT_PENDING = RESULT_ERROR + 8;
|
||||
|
||||
// fifth to sixth bit in addition indicate specific type of pending
|
||||
public static final int RESULT_PENDING_PASSPHRASE = RESULT_PENDING + 16;
|
||||
public static final int RESULT_PENDING_NFC = RESULT_PENDING + 32;
|
||||
|
||||
long mKeyIdPassphraseNeeded;
|
||||
|
||||
long mNfcKeyId;
|
||||
byte[] mNfcHash;
|
||||
int mNfcAlgo;
|
||||
Date mNfcTimestamp;
|
||||
String mNfcPassphrase;
|
||||
byte[] mDetachedSignature;
|
||||
|
||||
public long getKeyIdPassphraseNeeded() {
|
||||
return mKeyIdPassphraseNeeded;
|
||||
}
|
||||
|
||||
public void setKeyIdPassphraseNeeded(long keyIdPassphraseNeeded) {
|
||||
mKeyIdPassphraseNeeded = keyIdPassphraseNeeded;
|
||||
}
|
||||
|
||||
public void setNfcData(long nfcKeyId, byte[] nfcHash, int nfcAlgo, Date nfcTimestamp, String passphrase) {
|
||||
mNfcKeyId = nfcKeyId;
|
||||
mNfcHash = nfcHash;
|
||||
mNfcAlgo = nfcAlgo;
|
||||
mNfcTimestamp = nfcTimestamp;
|
||||
mNfcPassphrase = passphrase;
|
||||
}
|
||||
|
||||
public void setDetachedSignature(byte[] detachedSignature) {
|
||||
mDetachedSignature = detachedSignature;
|
||||
}
|
||||
|
||||
public long getNfcKeyId() {
|
||||
return mNfcKeyId;
|
||||
}
|
||||
|
||||
public byte[] getNfcHash() {
|
||||
return mNfcHash;
|
||||
}
|
||||
|
||||
public int getNfcAlgo() {
|
||||
return mNfcAlgo;
|
||||
}
|
||||
|
||||
public Date getNfcTimestamp() {
|
||||
return mNfcTimestamp;
|
||||
}
|
||||
|
||||
public String getNfcPassphrase() {
|
||||
return mNfcPassphrase;
|
||||
}
|
||||
|
||||
public byte[] getDetachedSignature() {
|
||||
return mDetachedSignature;
|
||||
}
|
||||
|
||||
public boolean isPending() {
|
||||
return (mResult & RESULT_PENDING) == RESULT_PENDING;
|
||||
}
|
||||
|
||||
public PgpSignEncryptResult(int result, OperationLog log) {
|
||||
super(result, log);
|
||||
}
|
||||
|
||||
public PgpSignEncryptResult(Parcel source) {
|
||||
super(source);
|
||||
mNfcHash = source.readInt() != 0 ? source.createByteArray() : null;
|
||||
mNfcAlgo = source.readInt();
|
||||
mNfcTimestamp = source.readInt() != 0 ? new Date(source.readLong()) : null;
|
||||
mDetachedSignature = source.readInt() != 0 ? source.createByteArray() : null;
|
||||
}
|
||||
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
if (mNfcHash != null) {
|
||||
dest.writeInt(1);
|
||||
dest.writeByteArray(mNfcHash);
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
dest.writeInt(mNfcAlgo);
|
||||
if (mNfcTimestamp != null) {
|
||||
dest.writeInt(1);
|
||||
dest.writeLong(mNfcTimestamp.getTime());
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
if (mDetachedSignature != null) {
|
||||
dest.writeInt(1);
|
||||
dest.writeByteArray(mDetachedSignature);
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
}
|
||||
|
||||
public static final Creator<PgpSignEncryptResult> CREATOR = new Creator<PgpSignEncryptResult>() {
|
||||
public PgpSignEncryptResult createFromParcel(final Parcel source) {
|
||||
return new PgpSignEncryptResult(source);
|
||||
}
|
||||
|
||||
public PgpSignEncryptResult[] newArray(final int size) {
|
||||
return new PgpSignEncryptResult[size];
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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 PromoteKeyResult extends OperationResult {
|
||||
|
||||
public final Long mMasterKeyId;
|
||||
|
||||
public PromoteKeyResult(int result, OperationLog log, Long masterKeyId) {
|
||||
super(result, log);
|
||||
mMasterKeyId = masterKeyId;
|
||||
}
|
||||
|
||||
public PromoteKeyResult(Parcel source) {
|
||||
super(source);
|
||||
mMasterKeyId = source.readLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
dest.writeLong(mMasterKeyId);
|
||||
}
|
||||
|
||||
public static Creator<PromoteKeyResult> CREATOR = new Creator<PromoteKeyResult>() {
|
||||
public PromoteKeyResult createFromParcel(final Parcel source) {
|
||||
return new PromoteKeyResult(source);
|
||||
}
|
||||
|
||||
public PromoteKeyResult[] newArray(final int size) {
|
||||
return new PromoteKeyResult[size];
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
+25
-69
@@ -19,74 +19,42 @@ package org.sufficientlysecure.keychain.operations.results;
|
||||
|
||||
import android.os.Parcel;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class SignEncryptResult extends OperationResult {
|
||||
|
||||
// the fourth bit indicates a "data pending" result! (it's also a form of non-success)
|
||||
ArrayList<PgpSignEncryptResult> mResults;
|
||||
byte[] mResultBytes;
|
||||
|
||||
public static final int RESULT_PENDING = RESULT_ERROR + 8;
|
||||
|
||||
// fifth to sixth bit in addition indicate specific type of pending
|
||||
public static final int RESULT_PENDING_PASSPHRASE = RESULT_PENDING + 16;
|
||||
public static final int RESULT_PENDING_NFC = RESULT_PENDING + 32;
|
||||
|
||||
long mKeyIdPassphraseNeeded;
|
||||
|
||||
long mNfcKeyId;
|
||||
byte[] mNfcHash;
|
||||
int mNfcAlgo;
|
||||
Date mNfcTimestamp;
|
||||
String mNfcPassphrase;
|
||||
|
||||
public long getKeyIdPassphraseNeeded() {
|
||||
return mKeyIdPassphraseNeeded;
|
||||
public PgpSignEncryptResult getPending() {
|
||||
for (PgpSignEncryptResult sub : mResults) {
|
||||
if (sub.isPending()) {
|
||||
return sub;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setKeyIdPassphraseNeeded(long keyIdPassphraseNeeded) {
|
||||
mKeyIdPassphraseNeeded = keyIdPassphraseNeeded;
|
||||
}
|
||||
|
||||
public void setNfcData(long nfcKeyId, byte[] nfcHash, int nfcAlgo, Date nfcTimestamp, String passphrase) {
|
||||
mNfcKeyId = nfcKeyId;
|
||||
mNfcHash = nfcHash;
|
||||
mNfcAlgo = nfcAlgo;
|
||||
mNfcTimestamp = nfcTimestamp;
|
||||
mNfcPassphrase = passphrase;
|
||||
}
|
||||
|
||||
public long getNfcKeyId() {
|
||||
return mNfcKeyId;
|
||||
}
|
||||
|
||||
public byte[] getNfcHash() {
|
||||
return mNfcHash;
|
||||
}
|
||||
|
||||
public int getNfcAlgo() {
|
||||
return mNfcAlgo;
|
||||
}
|
||||
|
||||
public Date getNfcTimestamp() {
|
||||
return mNfcTimestamp;
|
||||
}
|
||||
|
||||
public String getNfcPassphrase() {
|
||||
return mNfcPassphrase;
|
||||
}
|
||||
|
||||
public boolean isPending() {
|
||||
return (mResult & RESULT_PENDING) == RESULT_PENDING;
|
||||
}
|
||||
|
||||
public SignEncryptResult(int result, OperationLog log) {
|
||||
public SignEncryptResult(int result, OperationLog log, ArrayList<PgpSignEncryptResult> results) {
|
||||
super(result, log);
|
||||
mResults = results;
|
||||
}
|
||||
|
||||
public SignEncryptResult(int result, OperationLog log, ArrayList<PgpSignEncryptResult> results, byte[] resultBytes) {
|
||||
super(result, log);
|
||||
mResults = results;
|
||||
mResultBytes = resultBytes;
|
||||
}
|
||||
|
||||
public SignEncryptResult(Parcel source) {
|
||||
super(source);
|
||||
mNfcHash = source.readInt() != 0 ? source.createByteArray() : null;
|
||||
mNfcAlgo = source.readInt();
|
||||
mNfcTimestamp = source.readInt() != 0 ? new Date(source.readLong()) : null;
|
||||
mResults = source.createTypedArrayList(PgpSignEncryptResult.CREATOR);
|
||||
}
|
||||
|
||||
public byte[] getResultBytes() {
|
||||
return mResultBytes;
|
||||
}
|
||||
|
||||
public int describeContents() {
|
||||
@@ -95,19 +63,7 @@ public class SignEncryptResult extends OperationResult {
|
||||
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
if (mNfcHash != null) {
|
||||
dest.writeInt(1);
|
||||
dest.writeByteArray(mNfcHash);
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
dest.writeInt(mNfcAlgo);
|
||||
if (mNfcTimestamp != null) {
|
||||
dest.writeInt(1);
|
||||
dest.writeLong(mNfcTimestamp.getTime());
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
dest.writeTypedList(mResults);
|
||||
}
|
||||
|
||||
public static final Creator<SignEncryptResult> CREATOR = new Creator<SignEncryptResult>() {
|
||||
|
||||
-34
@@ -18,13 +18,8 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.operations.results;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Parcel;
|
||||
|
||||
import com.github.johnpersano.supertoasts.SuperCardToast;
|
||||
import com.github.johnpersano.supertoasts.SuperToast;
|
||||
import com.github.johnpersano.supertoasts.util.Style;
|
||||
|
||||
/** This is a simple subclass meant to contain only a single log message. This log
|
||||
* message is also shown without a log button in the createNotify SuperToast. */
|
||||
public class SingletonResult extends OperationResult {
|
||||
@@ -40,35 +35,6 @@ public class SingletonResult extends OperationResult {
|
||||
mLog.add(reason, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SuperCardToast createNotify(final Activity activity) {
|
||||
|
||||
// there is exactly one error msg - use that one
|
||||
String str = activity.getString(mLog.iterator().next().mType.getMsgId());
|
||||
int color;
|
||||
|
||||
// Determine color by result type
|
||||
if (cancelled()) {
|
||||
color = Style.RED;
|
||||
} else if (success()) {
|
||||
if (getLog().containsWarnings()) {
|
||||
color = Style.ORANGE;
|
||||
} else {
|
||||
color = Style.GREEN;
|
||||
}
|
||||
} else {
|
||||
color = Style.RED;
|
||||
}
|
||||
|
||||
SuperCardToast toast = new SuperCardToast(activity, SuperToast.Type.STANDARD,
|
||||
Style.getStyle(color, SuperToast.Animations.POPUP));
|
||||
toast.setText(str);
|
||||
toast.setDuration(SuperToast.Duration.EXTRA_LONG);
|
||||
toast.setIndeterminate(false);
|
||||
toast.setSwipeToDismiss(true);
|
||||
return toast;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
|
||||
+2
-4
@@ -19,7 +19,6 @@
|
||||
package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import org.spongycastle.openpgp.PGPKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
|
||||
@@ -80,9 +79,8 @@ public abstract class CanonicalizedKeyRing extends KeyRing {
|
||||
|
||||
public boolean isExpired() {
|
||||
// Is the master key expired?
|
||||
Date creationDate = getRing().getPublicKey().getCreationTime();
|
||||
Date expiryDate = getRing().getPublicKey().getValidSeconds() > 0
|
||||
? new Date(creationDate.getTime() + getRing().getPublicKey().getValidSeconds() * 1000) : null;
|
||||
Date creationDate = getPublicKey().getCreationTime();
|
||||
Date expiryDate = getPublicKey().getExpiryTime();
|
||||
|
||||
Date now = new Date();
|
||||
return creationDate.after(now) || (expiryDate != null && expiryDate.before(now));
|
||||
|
||||
+78
-4
@@ -20,8 +20,16 @@ package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPSignature;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Iterator;
|
||||
|
||||
/** Wrapper for a PGPPublicKey.
|
||||
*
|
||||
@@ -53,7 +61,7 @@ public class CanonicalizedPublicKey extends UncachedPublicKey {
|
||||
|
||||
public boolean canSign() {
|
||||
// if key flags subpacket is available, honor it!
|
||||
if (getKeyUsage() != null) {
|
||||
if (getKeyUsage() != 0) {
|
||||
return (getKeyUsage() & KeyFlags.SIGN_DATA) != 0;
|
||||
}
|
||||
|
||||
@@ -66,7 +74,7 @@ public class CanonicalizedPublicKey extends UncachedPublicKey {
|
||||
|
||||
public boolean canCertify() {
|
||||
// if key flags subpacket is available, honor it!
|
||||
if (getKeyUsage() != null) {
|
||||
if (getKeyUsage() != 0) {
|
||||
return (getKeyUsage() & KeyFlags.CERTIFY_OTHER) != 0;
|
||||
}
|
||||
|
||||
@@ -79,7 +87,7 @@ public class CanonicalizedPublicKey extends UncachedPublicKey {
|
||||
|
||||
public boolean canEncrypt() {
|
||||
// if key flags subpacket is available, honor it!
|
||||
if (getKeyUsage() != null) {
|
||||
if (getKeyUsage() != 0) {
|
||||
return (getKeyUsage() & (KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE)) != 0;
|
||||
}
|
||||
|
||||
@@ -93,13 +101,79 @@ public class CanonicalizedPublicKey extends UncachedPublicKey {
|
||||
|
||||
public boolean canAuthenticate() {
|
||||
// if key flags subpacket is available, honor it!
|
||||
if (getKeyUsage() != null) {
|
||||
if (getKeyUsage() != 0) {
|
||||
return (getKeyUsage() & KeyFlags.AUTHENTICATION) != 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isRevoked() {
|
||||
return mPublicKey.getSignaturesOfType(isMasterKey()
|
||||
? PGPSignature.KEY_REVOCATION
|
||||
: PGPSignature.SUBKEY_REVOCATION).hasNext();
|
||||
}
|
||||
|
||||
public boolean isExpired () {
|
||||
Date expiry = getExpiryTime();
|
||||
return expiry != null && expiry.before(new Date());
|
||||
}
|
||||
|
||||
public long getValidSeconds() {
|
||||
|
||||
long seconds;
|
||||
|
||||
// the getValidSeconds method is unreliable for master keys. we need to iterate all
|
||||
// user ids, then use the most recent certification from a non-revoked user id
|
||||
if (isMasterKey()) {
|
||||
Date latestCreation = null;
|
||||
seconds = 0;
|
||||
|
||||
for (byte[] rawUserId : getUnorderedRawUserIds()) {
|
||||
Iterator<WrappedSignature> sigs = getSignaturesForRawId(rawUserId);
|
||||
|
||||
// there is always a certification, so this call is safe
|
||||
WrappedSignature sig = sigs.next();
|
||||
|
||||
// we know a user id has at most two sigs: one certification, one revocation.
|
||||
// if the sig is a revocation, or there is another sig (which is a revocation),
|
||||
// the data in this uid is not relevant
|
||||
if (sig.isRevocation() || sigs.hasNext()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// this is our revocation, UNLESS there is a newer certificate!
|
||||
if (latestCreation == null || latestCreation.before(sig.getCreationTime())) {
|
||||
latestCreation = sig.getCreationTime();
|
||||
seconds = sig.getKeyExpirySeconds();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
seconds = mPublicKey.getValidSeconds();
|
||||
}
|
||||
|
||||
return seconds;
|
||||
}
|
||||
|
||||
public Date getExpiryTime() {
|
||||
long seconds = getValidSeconds();
|
||||
|
||||
if (seconds > Integer.MAX_VALUE) {
|
||||
Log.e(Constants.TAG, "error, expiry time too large");
|
||||
return null;
|
||||
}
|
||||
if (seconds == 0) {
|
||||
// no expiry
|
||||
return null;
|
||||
}
|
||||
Date creationDate = getCreationTime();
|
||||
Calendar calendar = GregorianCalendar.getInstance();
|
||||
calendar.setTime(creationDate);
|
||||
calendar.add(Calendar.SECOND, (int) seconds);
|
||||
|
||||
return calendar.getTime();
|
||||
}
|
||||
|
||||
/** Same method as superclass, but we make it public. */
|
||||
public Integer getKeyUsage() {
|
||||
return super.getKeyUsage();
|
||||
|
||||
+12
-1
@@ -18,9 +18,11 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import org.spongycastle.bcpg.S2K;
|
||||
import org.spongycastle.openpgp.PGPObjectFactory;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
|
||||
@@ -76,7 +78,7 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing {
|
||||
public IterableIterator<CanonicalizedPublicKey> publicKeyIterator() {
|
||||
@SuppressWarnings("unchecked")
|
||||
final Iterator<PGPPublicKey> it = getRing().getPublicKeys();
|
||||
return new IterableIterator<CanonicalizedPublicKey>(new Iterator<CanonicalizedPublicKey>() {
|
||||
return new IterableIterator<>(new Iterator<CanonicalizedPublicKey>() {
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return it.hasNext();
|
||||
@@ -94,4 +96,13 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing {
|
||||
});
|
||||
}
|
||||
|
||||
/** Create a dummy secret ring from this key */
|
||||
public UncachedKeyRing createDummySecretRing () {
|
||||
|
||||
PGPSecretKeyRing secRing = PGPSecretKeyRing.constructDummyFromPublic(getRing(),
|
||||
S2K.GNU_PROTECTION_MODE_NO_PRIVATE_KEY);
|
||||
return new UncachedKeyRing(secRing);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+3
-5
@@ -182,7 +182,7 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
|
||||
* @return
|
||||
*/
|
||||
public LinkedList<Integer> getSupportedHashAlgorithms() {
|
||||
LinkedList<Integer> supported = new LinkedList<Integer>();
|
||||
LinkedList<Integer> supported = new LinkedList<>();
|
||||
|
||||
if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) {
|
||||
// No support for MD5
|
||||
@@ -247,7 +247,7 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
|
||||
|
||||
int signatureType;
|
||||
if (cleartext) {
|
||||
// for sign-only ascii text
|
||||
// for sign-only ascii text (cleartext signature)
|
||||
signatureType = PGPSignature.CANONICAL_TEXT_DOCUMENT;
|
||||
} else {
|
||||
signatureType = PGPSignature.BINARY_DOCUMENT;
|
||||
@@ -262,11 +262,9 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
|
||||
spGen.setSignatureCreationTime(false, nfcCreationTimestamp);
|
||||
signatureGenerator.setHashedSubpackets(spGen.generate());
|
||||
return signatureGenerator;
|
||||
} catch (PgpKeyNotFoundException e) {
|
||||
} catch (PgpKeyNotFoundException | PGPException e) {
|
||||
// TODO: simply throw PGPException!
|
||||
throw new PgpGeneralException("Error initializing signature!", e);
|
||||
} catch (PGPException e) {
|
||||
throw new PgpGeneralException("Error initializing signature!", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-11
@@ -18,27 +18,19 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import org.spongycastle.bcpg.S2K;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPKeyRing;
|
||||
import org.spongycastle.openpgp.PGPObjectFactory;
|
||||
import org.spongycastle.openpgp.PGPPrivateKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSignature;
|
||||
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
|
||||
public class CanonicalizedSecretKeyRing extends CanonicalizedKeyRing {
|
||||
@@ -94,7 +86,7 @@ public class CanonicalizedSecretKeyRing extends CanonicalizedKeyRing {
|
||||
|
||||
public IterableIterator<CanonicalizedSecretKey> secretKeyIterator() {
|
||||
final Iterator<PGPSecretKey> it = mRing.getSecretKeys();
|
||||
return new IterableIterator<CanonicalizedSecretKey>(new Iterator<CanonicalizedSecretKey>() {
|
||||
return new IterableIterator<>(new Iterator<CanonicalizedSecretKey>() {
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return it.hasNext();
|
||||
@@ -114,7 +106,7 @@ public class CanonicalizedSecretKeyRing extends CanonicalizedKeyRing {
|
||||
|
||||
public IterableIterator<CanonicalizedPublicKey> publicKeyIterator() {
|
||||
final Iterator<PGPPublicKey> it = getRing().getPublicKeys();
|
||||
return new IterableIterator<CanonicalizedPublicKey>(new Iterator<CanonicalizedPublicKey>() {
|
||||
return new IterableIterator<>(new Iterator<CanonicalizedPublicKey>() {
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return it.hasNext();
|
||||
@@ -133,7 +125,7 @@ public class CanonicalizedSecretKeyRing extends CanonicalizedKeyRing {
|
||||
}
|
||||
|
||||
public HashMap<String,String> getLocalNotationData() {
|
||||
HashMap<String,String> result = new HashMap<String,String>();
|
||||
HashMap<String,String> result = new HashMap<>();
|
||||
Iterator<PGPSignature> it = getRing().getPublicKey().getKeySignatures();
|
||||
while (it.hasNext()) {
|
||||
WrappedSignature sig = new WrappedSignature(it.next());
|
||||
|
||||
+3
-4
@@ -19,7 +19,6 @@ package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
@@ -33,7 +32,7 @@ public class OpenPgpSignatureResultBuilder {
|
||||
// OpenPgpSignatureResult
|
||||
private boolean mSignatureOnly = false;
|
||||
private String mPrimaryUserId;
|
||||
private ArrayList<String> mUserIds = new ArrayList<String>();
|
||||
private ArrayList<String> mUserIds = new ArrayList<>();
|
||||
private long mKeyId;
|
||||
|
||||
// builder
|
||||
@@ -105,8 +104,8 @@ public class OpenPgpSignatureResultBuilder {
|
||||
setUserIds(signingRing.getUnorderedUserIds());
|
||||
|
||||
// either master key is expired/revoked or this specific subkey is expired/revoked
|
||||
setKeyExpired(signingRing.isExpired() || signingKey.isExpired());
|
||||
setKeyRevoked(signingRing.isRevoked() || signingKey.isRevoked());
|
||||
setKeyExpired(signingRing.isExpired() || signingKey.isMaybeExpired());
|
||||
setKeyRevoked(signingRing.isRevoked() || signingKey.isMaybeRevoked());
|
||||
}
|
||||
|
||||
public OpenPgpSignatureResult build() {
|
||||
|
||||
+391
-97
@@ -22,13 +22,13 @@ import android.content.Context;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import org.openintents.openpgp.OpenPgpMetadata;
|
||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||
import org.spongycastle.bcpg.ArmoredInputStream;
|
||||
import org.spongycastle.openpgp.PGPCompressedData;
|
||||
import org.spongycastle.openpgp.PGPEncryptedData;
|
||||
import org.spongycastle.openpgp.PGPEncryptedDataList;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPLiteralData;
|
||||
import org.spongycastle.openpgp.PGPObjectFactory;
|
||||
import org.spongycastle.openpgp.PGPOnePassSignature;
|
||||
import org.spongycastle.openpgp.PGPOnePassSignatureList;
|
||||
import org.spongycastle.openpgp.PGPPBEEncryptedData;
|
||||
@@ -36,10 +36,10 @@ 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.operator.PBEDataDecryptorFactory;
|
||||
import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider;
|
||||
import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder;
|
||||
@@ -48,11 +48,15 @@ import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.BaseOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
@@ -83,6 +87,9 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
private Set<Long> mAllowedKeyIds;
|
||||
private boolean mDecryptMetadataOnly;
|
||||
private byte[] mDecryptedSessionKey;
|
||||
private byte[] mDetachedSignature;
|
||||
private String mRequiredSignerFingerprint;
|
||||
private boolean mSignedLiteralData;
|
||||
|
||||
protected PgpDecryptVerify(Builder builder) {
|
||||
super(builder.mContext, builder.mProviderHelper, builder.mProgressable);
|
||||
@@ -96,6 +103,9 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
this.mAllowedKeyIds = builder.mAllowedKeyIds;
|
||||
this.mDecryptMetadataOnly = builder.mDecryptMetadataOnly;
|
||||
this.mDecryptedSessionKey = builder.mDecryptedSessionKey;
|
||||
this.mDetachedSignature = builder.mDetachedSignature;
|
||||
this.mSignedLiteralData = builder.mSignedLiteralData;
|
||||
this.mRequiredSignerFingerprint = builder.mRequiredSignerFingerprint;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
@@ -103,15 +113,18 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
private Context mContext;
|
||||
private ProviderHelper mProviderHelper;
|
||||
private InputData mData;
|
||||
private OutputStream mOutStream;
|
||||
|
||||
// optional
|
||||
private OutputStream mOutStream = null;
|
||||
private Progressable mProgressable = null;
|
||||
private boolean mAllowSymmetricDecryption = true;
|
||||
private String mPassphrase = null;
|
||||
private Set<Long> mAllowedKeyIds = null;
|
||||
private boolean mDecryptMetadataOnly = false;
|
||||
private byte[] mDecryptedSessionKey = null;
|
||||
private byte[] mDetachedSignature = null;
|
||||
private String mRequiredSignerFingerprint = null;
|
||||
private boolean mSignedLiteralData = false;
|
||||
|
||||
public Builder(Context context, ProviderHelper providerHelper,
|
||||
Progressable progressable,
|
||||
@@ -123,6 +136,24 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
mOutStream = outStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used when verifying signed literals to check that they are signed with
|
||||
* the required key
|
||||
*/
|
||||
public Builder setRequiredSignerFingerprint(String fingerprint) {
|
||||
mRequiredSignerFingerprint = fingerprint;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is to force a mode where the message is just the signature key id and
|
||||
* then a literal data packet; used in Keybase.io proofs
|
||||
*/
|
||||
public Builder setSignedLiteralData(boolean signedLiteralData) {
|
||||
mSignedLiteralData = signedLiteralData;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setAllowSymmetricDecryption(boolean allowSymmetricDecryption) {
|
||||
mAllowSymmetricDecryption = allowSymmetricDecryption;
|
||||
return this;
|
||||
@@ -156,6 +187,14 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* If detachedSignature != null, it will be used exclusively to verify the signature
|
||||
*/
|
||||
public Builder setDetachedSignature(byte[] detachedSignature) {
|
||||
mDetachedSignature = detachedSignature;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PgpDecryptVerify build() {
|
||||
return new PgpDecryptVerify(this);
|
||||
}
|
||||
@@ -166,22 +205,32 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
*/
|
||||
public DecryptVerifyResult execute() {
|
||||
try {
|
||||
// automatically works with ascii armor input and binary
|
||||
InputStream in = PGPUtil.getDecoderStream(mData.getInputStream());
|
||||
if (mDetachedSignature != null) {
|
||||
Log.d(Constants.TAG, "Detached signature present, verifying with this signature only");
|
||||
|
||||
if (in instanceof ArmoredInputStream) {
|
||||
ArmoredInputStream aIn = (ArmoredInputStream) in;
|
||||
// it is ascii armored
|
||||
Log.d(Constants.TAG, "ASCII Armor Header Line: " + aIn.getArmorHeaderLine());
|
||||
return verifyDetachedSignature(mData.getInputStream(), 0);
|
||||
} else {
|
||||
// automatically works with PGP ascii armor and PGP binary
|
||||
InputStream in = PGPUtil.getDecoderStream(mData.getInputStream());
|
||||
|
||||
if (aIn.isClearText()) {
|
||||
// a cleartext signature, verify it with the other method
|
||||
return verifyCleartextSignature(aIn, 0);
|
||||
if (in instanceof ArmoredInputStream) {
|
||||
ArmoredInputStream aIn = (ArmoredInputStream) in;
|
||||
// it is ascii armored
|
||||
Log.d(Constants.TAG, "ASCII Armor Header Line: " + aIn.getArmorHeaderLine());
|
||||
|
||||
if (mSignedLiteralData) {
|
||||
return verifySignedLiteralData(aIn, 0);
|
||||
} else if (aIn.isClearText()) {
|
||||
// a cleartext signature, verify it with the other method
|
||||
return verifyCleartextSignature(aIn, 0);
|
||||
} else {
|
||||
// else: ascii armored encryption! go on...
|
||||
return decryptVerify(in, 0);
|
||||
}
|
||||
} else {
|
||||
return decryptVerify(in, 0);
|
||||
}
|
||||
// else: ascii armored encryption! go on...
|
||||
}
|
||||
|
||||
return decryptVerify(in, 0);
|
||||
} catch (PGPException e) {
|
||||
Log.d(Constants.TAG, "PGPException", e);
|
||||
OperationLog log = new OperationLog();
|
||||
@@ -195,6 +244,136 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify Keybase.io style signed literal data
|
||||
*/
|
||||
private DecryptVerifyResult verifySignedLiteralData(InputStream in, 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 (!(mRequiredSignerFingerprint.equals(fingerprint))) {
|
||||
log.add(LogType.MSG_VL_ERROR_MISSING_KEY, indent);
|
||||
Log.d(Constants.TAG, "Fingerprint mismatch; wanted " + mRequiredSignerFingerprint +
|
||||
" 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) {
|
||||
mOutStream.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);
|
||||
|
||||
// these are not cleartext signatures!
|
||||
// TODO: what about binary signatures?
|
||||
signatureResultBuilder.setSignatureOnly(false);
|
||||
|
||||
// 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.getStatus() != OpenPgpSignatureResult.SIGNATURE_SUCCESS_CERTIFIED
|
||||
&& signatureResult.getStatus() != OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED) {
|
||||
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);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Decrypt and/or verifies binary or ascii armored pgp
|
||||
*/
|
||||
@@ -205,7 +384,7 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
log.add(LogType.MSG_DC, indent);
|
||||
indent += 1;
|
||||
|
||||
PGPObjectFactory pgpF = new PGPObjectFactory(in, new JcaKeyFingerprintCalculator());
|
||||
JcaPGPObjectFactory pgpF = new JcaPGPObjectFactory(in);
|
||||
PGPEncryptedDataList enc;
|
||||
Object o = pgpF.nextObject();
|
||||
|
||||
@@ -234,6 +413,25 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
boolean symmetricPacketFound = false;
|
||||
boolean anyPacketFound = false;
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// go through all objects and find one we can decrypt
|
||||
while (it.hasNext()) {
|
||||
Object obj = it.next();
|
||||
@@ -257,19 +455,19 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
);
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
// continue with the next packet in the while loop
|
||||
log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent +1);
|
||||
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);
|
||||
log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent + 1);
|
||||
continue;
|
||||
}
|
||||
// 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);
|
||||
log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent + 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -283,7 +481,7 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
if (!mAllowedKeyIds.contains(masterKeyId)) {
|
||||
// this key is in our db, but NOT allowed!
|
||||
// continue with the next packet in the while loop
|
||||
log.add(LogType.MSG_DC_ASKIP_NOT_ALLOWED, indent +1);
|
||||
log.add(LogType.MSG_DC_ASKIP_NOT_ALLOWED, indent + 1);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -298,15 +496,15 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
try {
|
||||
// returns "" if key has no passphrase
|
||||
mPassphrase = getCachedPassphrase(subKeyId);
|
||||
log.add(LogType.MSG_DC_PASS_CACHED, indent +1);
|
||||
log.add(LogType.MSG_DC_PASS_CACHED, indent + 1);
|
||||
} catch (PassphraseCacheInterface.NoSecretKeyException e) {
|
||||
log.add(LogType.MSG_DC_ERROR_NO_KEY, indent +1);
|
||||
log.add(LogType.MSG_DC_ERROR_NO_KEY, indent + 1);
|
||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
// if passphrase was not cached, return here indicating that a passphrase is missing!
|
||||
if (mPassphrase == null) {
|
||||
log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent +1);
|
||||
log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1);
|
||||
DecryptVerifyResult result =
|
||||
new DecryptVerifyResult(DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE, log);
|
||||
result.setKeyIdPassphraseNeeded(subKeyId);
|
||||
@@ -322,8 +520,8 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
|
||||
log.add(LogType.MSG_DC_SYM, indent);
|
||||
|
||||
if (! mAllowSymmetricDecryption) {
|
||||
log.add(LogType.MSG_DC_SYM_SKIP, indent +1);
|
||||
if (!mAllowSymmetricDecryption) {
|
||||
log.add(LogType.MSG_DC_SYM_SKIP, indent + 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -338,7 +536,7 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
// if no passphrase is given, return here
|
||||
// indicating that a passphrase is missing!
|
||||
if (mPassphrase == null) {
|
||||
log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent +1);
|
||||
log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1);
|
||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE, log);
|
||||
}
|
||||
|
||||
@@ -383,13 +581,13 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
updateProgress(R.string.progress_extracting_key, currentProgress, 100);
|
||||
|
||||
try {
|
||||
log.add(LogType.MSG_DC_UNLOCKING, indent +1);
|
||||
log.add(LogType.MSG_DC_UNLOCKING, indent + 1);
|
||||
if (!secretEncryptionKey.unlock(mPassphrase)) {
|
||||
log.add(LogType.MSG_DC_ERROR_BAD_PASSPHRASE, indent +1);
|
||||
log.add(LogType.MSG_DC_ERROR_BAD_PASSPHRASE, indent + 1);
|
||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
||||
}
|
||||
} catch (PgpGeneralException e) {
|
||||
log.add(LogType.MSG_DC_ERROR_EXTRACT_KEY, indent +1);
|
||||
log.add(LogType.MSG_DC_ERROR_EXTRACT_KEY, indent + 1);
|
||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
@@ -401,7 +599,7 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
= secretEncryptionKey.getDecryptorFactory(mDecryptedSessionKey);
|
||||
clear = encryptedDataAsymmetric.getDataStream(decryptorFactory);
|
||||
} catch (NfcSyncPublicKeyDataDecryptorFactoryBuilder.NfcInteractionNeeded e) {
|
||||
log.add(LogType.MSG_DC_PENDING_NFC, indent +1);
|
||||
log.add(LogType.MSG_DC_PENDING_NFC, indent + 1);
|
||||
DecryptVerifyResult result =
|
||||
new DecryptVerifyResult(DecryptVerifyResult.RESULT_PENDING_NFC, log);
|
||||
result.setNfcState(secretEncryptionKey.getKeyId(), e.encryptedSessionKey, mPassphrase);
|
||||
@@ -412,11 +610,11 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
// If we didn't find any useful data, error out
|
||||
// no packet has been found where we have the corresponding secret key in our db
|
||||
log.add(
|
||||
anyPacketFound ? LogType.MSG_DC_ERROR_NO_KEY : LogType.MSG_DC_ERROR_NO_DATA, indent +1);
|
||||
anyPacketFound ? LogType.MSG_DC_ERROR_NO_KEY : LogType.MSG_DC_ERROR_NO_DATA, indent + 1);
|
||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
PGPObjectFactory plainFact = new PGPObjectFactory(clear, new JcaKeyFingerprintCalculator());
|
||||
JcaPGPObjectFactory plainFact = new JcaPGPObjectFactory(clear);
|
||||
Object dataChunk = plainFact.nextObject();
|
||||
OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder();
|
||||
int signatureIndex = -1;
|
||||
@@ -427,25 +625,27 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
indent += 1;
|
||||
|
||||
if (dataChunk instanceof PGPCompressedData) {
|
||||
log.add(LogType.MSG_DC_CLEAR_DECOMPRESS, indent +1);
|
||||
log.add(LogType.MSG_DC_CLEAR_DECOMPRESS, indent + 1);
|
||||
currentProgress += 2;
|
||||
updateProgress(R.string.progress_decompressing_data, currentProgress, 100);
|
||||
|
||||
PGPCompressedData compressedData = (PGPCompressedData) dataChunk;
|
||||
|
||||
PGPObjectFactory fact = new PGPObjectFactory(compressedData.getDataStream(), new JcaKeyFingerprintCalculator());
|
||||
JcaPGPObjectFactory fact = new JcaPGPObjectFactory(compressedData.getDataStream());
|
||||
dataChunk = fact.nextObject();
|
||||
plainFact = fact;
|
||||
}
|
||||
|
||||
PGPOnePassSignature signature = null;
|
||||
if (dataChunk instanceof PGPOnePassSignatureList) {
|
||||
log.add(LogType.MSG_DC_CLEAR_SIGNATURE, indent +1);
|
||||
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) {
|
||||
@@ -491,19 +691,15 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
OpenPgpMetadata metadata;
|
||||
|
||||
if (dataChunk instanceof PGPLiteralData) {
|
||||
log.add(LogType.MSG_DC_CLEAR_DATA, indent +1);
|
||||
log.add(LogType.MSG_DC_CLEAR_DATA, indent + 1);
|
||||
indent += 2;
|
||||
currentProgress += 4;
|
||||
updateProgress(R.string.progress_decrypting, currentProgress, 100);
|
||||
|
||||
PGPLiteralData literalData = (PGPLiteralData) dataChunk;
|
||||
|
||||
// TODO: how to get the real original size?
|
||||
// this is the encrypted size so if we enable compression this value is wrong!
|
||||
long originalSize = mData.getSize() - mData.getStreamPosition();
|
||||
if (originalSize < 0) {
|
||||
originalSize = 0;
|
||||
}
|
||||
// reported size may be null if partial packets are involved (highly unlikely though)
|
||||
Long originalSize = literalData.getDataLengthIfAvailable();
|
||||
|
||||
String originalFilename = literalData.getFileName();
|
||||
String mimeType = null;
|
||||
@@ -531,18 +727,20 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
originalFilename,
|
||||
mimeType,
|
||||
literalData.getModificationTime().getTime(),
|
||||
originalSize);
|
||||
originalSize == null ? 0 : originalSize);
|
||||
|
||||
if ( ! originalFilename.equals("")) {
|
||||
if (!"".equals(originalFilename)) {
|
||||
log.add(LogType.MSG_DC_CLEAR_META_FILE, indent + 1, originalFilename);
|
||||
}
|
||||
log.add(LogType.MSG_DC_CLEAR_META_MIME, indent +1,
|
||||
log.add(LogType.MSG_DC_CLEAR_META_MIME, indent + 1,
|
||||
mimeType);
|
||||
log.add(LogType.MSG_DC_CLEAR_META_TIME, indent +1,
|
||||
log.add(LogType.MSG_DC_CLEAR_META_TIME, indent + 1,
|
||||
new Date(literalData.getModificationTime().getTime()).toString());
|
||||
if (originalSize != 0) {
|
||||
if (originalSize != null) {
|
||||
log.add(LogType.MSG_DC_CLEAR_META_SIZE, indent + 1,
|
||||
Long.toString(originalSize));
|
||||
} else {
|
||||
log.add(LogType.MSG_DC_CLEAR_META_SIZE_UNKNOWN, indent + 1);
|
||||
}
|
||||
|
||||
// return here if we want to decrypt the metadata only
|
||||
@@ -550,6 +748,7 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
log.add(LogType.MSG_DC_OK_META_ONLY, indent);
|
||||
DecryptVerifyResult result =
|
||||
new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
|
||||
result.setCharset(charset);
|
||||
result.setDecryptMetadata(metadata);
|
||||
return result;
|
||||
}
|
||||
@@ -572,7 +771,10 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
int length;
|
||||
byte[] buffer = new byte[1 << 16];
|
||||
while ((length = dataIn.read(buffer)) > 0) {
|
||||
mOutStream.write(buffer, 0, length);
|
||||
Log.d(Constants.TAG, "read bytes: " + length);
|
||||
if (mOutStream != null) {
|
||||
mOutStream.write(buffer, 0, length);
|
||||
}
|
||||
|
||||
// update signature buffer if signature is also present
|
||||
if (signature != null) {
|
||||
@@ -587,9 +789,8 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
progress = 100;
|
||||
}
|
||||
progressScaler.setProgress((int) progress, 100);
|
||||
} else {
|
||||
// TODO: slow annealing to fake a progress?
|
||||
}
|
||||
// TODO: slow annealing to fake a progress?
|
||||
}
|
||||
|
||||
if (signature != null) {
|
||||
@@ -606,9 +807,9 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
// Verify signature and check binding signatures
|
||||
boolean validSignature = signature.verify(messageSignature);
|
||||
if (validSignature) {
|
||||
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_OK, indent +1);
|
||||
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_OK, indent + 1);
|
||||
} else {
|
||||
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_BAD, indent +1);
|
||||
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_BAD, indent + 1);
|
||||
}
|
||||
signatureResultBuilder.setValidSignature(validSignature);
|
||||
}
|
||||
@@ -632,6 +833,7 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
// If no valid signature is present:
|
||||
// Handle missing integrity protection like failed integrity protection!
|
||||
// The MDC packet can be stripped by an attacker!
|
||||
Log.d(Constants.TAG, "MDC fail");
|
||||
if (!signatureResultBuilder.isValidSignature()) {
|
||||
log.add(LogType.MSG_DC_ERROR_INTEGRITY_MISSING, indent);
|
||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
||||
@@ -647,6 +849,7 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
|
||||
result.setDecryptMetadata(metadata);
|
||||
result.setSignatureResult(signatureResultBuilder.build());
|
||||
result.setCharset(charset);
|
||||
return result;
|
||||
|
||||
}
|
||||
@@ -669,7 +872,7 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
|
||||
updateProgress(R.string.progress_done, 0, 100);
|
||||
updateProgress(R.string.progress_reading_data, 0, 100);
|
||||
|
||||
ByteArrayOutputStream lineOut = new ByteArrayOutputStream();
|
||||
int lookAhead = readInputLine(lineOut, aIn);
|
||||
@@ -689,10 +892,12 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
out.close();
|
||||
|
||||
byte[] clearText = out.toByteArray();
|
||||
mOutStream.write(clearText);
|
||||
if (mOutStream != null) {
|
||||
mOutStream.write(clearText);
|
||||
}
|
||||
|
||||
updateProgress(R.string.progress_processing_signature, 60, 100);
|
||||
PGPObjectFactory pgpFact = new PGPObjectFactory(aIn, new JcaKeyFingerprintCalculator());
|
||||
JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(aIn);
|
||||
|
||||
PGPSignatureList sigList = (PGPSignatureList) pgpFact.nextObject();
|
||||
if (sigList == null) {
|
||||
@@ -700,45 +905,7 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
PGPSignature signature = processPGPSignatureList(sigList, signatureResultBuilder);
|
||||
|
||||
if (signature != null) {
|
||||
try {
|
||||
@@ -786,6 +953,133 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
return result;
|
||||
}
|
||||
|
||||
private DecryptVerifyResult verifyDetachedSignature(InputStream in, int indent)
|
||||
throws IOException, PGPException {
|
||||
|
||||
OperationLog log = new OperationLog();
|
||||
|
||||
OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder();
|
||||
// detached signatures are never encrypted
|
||||
signatureResultBuilder.setSignatureOnly(true);
|
||||
|
||||
updateProgress(R.string.progress_processing_signature, 0, 100);
|
||||
InputStream detachedSigIn = new ByteArrayInputStream(mDetachedSignature);
|
||||
detachedSigIn = PGPUtil.getDecoderStream(detachedSigIn);
|
||||
|
||||
JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(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 {
|
||||
log.add(LogType.MSG_DC_ERROR_INVALID_SIGLIST, 0);
|
||||
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
PGPSignature signature = processPGPSignatureList(sigList, signatureResultBuilder);
|
||||
|
||||
if (signature != null) {
|
||||
updateProgress(R.string.progress_reading_data, 60, 100);
|
||||
|
||||
ProgressScaler progressScaler = new ProgressScaler(mProgressable, 60, 90, 100);
|
||||
long alreadyWritten = 0;
|
||||
long wholeSize = mData.getSize() - mData.getStreamPosition();
|
||||
int length;
|
||||
byte[] buffer = new byte[1 << 16];
|
||||
while ((length = in.read(buffer)) > 0) {
|
||||
if (mOutStream != null) {
|
||||
mOutStream.write(buffer, 0, length);
|
||||
}
|
||||
|
||||
// update signature buffer if signature is also present
|
||||
signature.update(buffer, 0, length);
|
||||
|
||||
alreadyWritten += length;
|
||||
if (wholeSize > 0) {
|
||||
long progress = 100 * alreadyWritten / wholeSize;
|
||||
// stop at 100% for wrong file sizes...
|
||||
if (progress > 100) {
|
||||
progress = 100;
|
||||
}
|
||||
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);
|
||||
|
||||
// these are not cleartext signatures!
|
||||
signatureResultBuilder.setSignatureOnly(false);
|
||||
|
||||
// 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);
|
||||
}
|
||||
signatureResultBuilder.setValidSignature(validSignature);
|
||||
}
|
||||
|
||||
updateProgress(R.string.progress_done, 100, 100);
|
||||
|
||||
log.add(LogType.MSG_DC_OK, indent);
|
||||
|
||||
DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
|
||||
result.setSignatureResult(signatureResultBuilder.build());
|
||||
return result;
|
||||
}
|
||||
|
||||
private PGPSignature processPGPSignatureList(PGPSignatureList sigList, OpenPgpSignatureResultBuilder signatureResultBuilder) 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());
|
||||
}
|
||||
}
|
||||
|
||||
return signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mostly taken from ClearSignedFileProcessor in Bouncy Castle
|
||||
*/
|
||||
@@ -807,7 +1101,7 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
while ((ch = fIn.read()) >= 0) {
|
||||
bOut.write(ch);
|
||||
if (ch == '\r' || ch == '\n') {
|
||||
lookAhead = readPassedEOL(bOut, ch, fIn);
|
||||
lookAhead = readPastEOL(bOut, ch, fIn);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -824,7 +1118,7 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
do {
|
||||
bOut.write(ch);
|
||||
if (ch == '\r' || ch == '\n') {
|
||||
lookAhead = readPassedEOL(bOut, ch, fIn);
|
||||
lookAhead = readPastEOL(bOut, ch, fIn);
|
||||
break;
|
||||
}
|
||||
} while ((ch = fIn.read()) >= 0);
|
||||
@@ -836,7 +1130,7 @@ public class PgpDecryptVerify extends BaseOperation {
|
||||
return lookAhead;
|
||||
}
|
||||
|
||||
private static int readPassedEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn)
|
||||
private static int readPastEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn)
|
||||
throws IOException {
|
||||
int lookAhead = fIn.read();
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@ import android.content.pm.PackageManager.NameNotFoundException;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
+101
-31
@@ -135,7 +135,7 @@ public class PgpKeyOperation {
|
||||
public PgpKeyOperation(Progressable progress) {
|
||||
super();
|
||||
if (progress != null) {
|
||||
mProgress = new Stack<Progressable>();
|
||||
mProgress = new Stack<>();
|
||||
mProgress.push(progress);
|
||||
}
|
||||
}
|
||||
@@ -288,13 +288,11 @@ public class PgpKeyOperation {
|
||||
// build new key pair
|
||||
return new JcaPGPKeyPair(algorithm, keyGen.generateKeyPair(), new Date());
|
||||
|
||||
} catch(NoSuchProviderException e) {
|
||||
} catch(NoSuchProviderException | InvalidAlgorithmParameterException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch(NoSuchAlgorithmException e) {
|
||||
log.add(LogType.MSG_CR_ERROR_UNKNOWN_ALGO, indent);
|
||||
return null;
|
||||
} catch(InvalidAlgorithmParameterException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch(PGPException e) {
|
||||
Log.e(Constants.TAG, "internal pgp error", e);
|
||||
log.add(LogType.MSG_CR_ERROR_INTERNAL_PGP, indent);
|
||||
@@ -389,6 +387,9 @@ public class PgpKeyOperation {
|
||||
* with a passphrase fails, the operation will fail with an unlocking error. More specific
|
||||
* handling of errors should be done in UI code!
|
||||
*
|
||||
* If the passphrase is null, only a restricted subset of operations will be available,
|
||||
* namely stripping of subkeys and changing the protection mode of dummy keys.
|
||||
*
|
||||
*/
|
||||
public PgpEditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR, SaveKeyringParcel saveParcel,
|
||||
String passphrase) {
|
||||
@@ -429,12 +430,17 @@ public class PgpKeyOperation {
|
||||
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
// If we have no passphrase, only allow restricted operation
|
||||
if (passphrase == null) {
|
||||
return internalRestricted(sKR, saveParcel, log);
|
||||
}
|
||||
|
||||
// read masterKeyFlags, and use the same as before.
|
||||
// since this is the master key, this contains at least CERTIFY_OTHER
|
||||
PGPPublicKey masterPublicKey = masterSecretKey.getPublicKey();
|
||||
int masterKeyFlags = readKeyFlags(masterPublicKey) | KeyFlags.CERTIFY_OTHER;
|
||||
long masterKeyExpiry = masterPublicKey.getValidSeconds() == 0L ? 0L :
|
||||
masterPublicKey.getCreationTime().getTime() / 1000 + masterPublicKey.getValidSeconds();
|
||||
Date expiryTime = wsKR.getPublicKey().getExpiryTime();
|
||||
long masterKeyExpiry = expiryTime != null ? expiryTime.getTime() / 1000 : 0L;
|
||||
|
||||
return internal(sKR, masterSecretKey, masterKeyFlags, masterKeyExpiry, saveParcel, passphrase, log);
|
||||
|
||||
@@ -496,7 +502,7 @@ public class PgpKeyOperation {
|
||||
@SuppressWarnings("unchecked")
|
||||
Iterator<PGPSignature> it = modifiedPublicKey.getSignaturesForID(userId);
|
||||
if (it != null) {
|
||||
for (PGPSignature cert : new IterableIterator<PGPSignature>(it)) {
|
||||
for (PGPSignature cert : new IterableIterator<>(it)) {
|
||||
if (cert.getKeyID() != masterPublicKey.getKeyID()) {
|
||||
// foreign certificate?! error error error
|
||||
log.add(LogType.MSG_MF_ERROR_INTEGRITY, indent);
|
||||
@@ -715,6 +721,27 @@ public class PgpKeyOperation {
|
||||
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
if (change.mDummyStrip || change.mDummyDivert != null) {
|
||||
// IT'S DANGEROUS~
|
||||
// no really, it is. this operation irrevocably removes the private key data from the key
|
||||
if (change.mDummyStrip) {
|
||||
sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey());
|
||||
} else {
|
||||
// the serial number must be 16 bytes in length
|
||||
if (change.mDummyDivert.length != 16) {
|
||||
log.add(LogType.MSG_MF_ERROR_DIVERT_SERIAL,
|
||||
indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
|
||||
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
}
|
||||
sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
|
||||
}
|
||||
|
||||
// This doesn't concern us any further
|
||||
if (!change.mRecertify && (change.mExpiry == null && change.mFlags == null)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// expiry must not be in the past
|
||||
if (change.mExpiry != null && change.mExpiry != 0 &&
|
||||
new Date(change.mExpiry*1000).before(new Date())) {
|
||||
@@ -805,30 +832,6 @@ public class PgpKeyOperation {
|
||||
}
|
||||
subProgressPop();
|
||||
|
||||
// 4c. For each subkey to be stripped... do so
|
||||
subProgressPush(65, 70);
|
||||
for (int i = 0; i < saveParcel.mStripSubKeys.size(); i++) {
|
||||
|
||||
progress(R.string.progress_modify_subkeystrip, (i-1) * (100 / saveParcel.mStripSubKeys.size()));
|
||||
long strip = saveParcel.mStripSubKeys.get(i);
|
||||
log.add(LogType.MSG_MF_SUBKEY_STRIP,
|
||||
indent, KeyFormattingUtils.convertKeyIdToHex(strip));
|
||||
|
||||
PGPSecretKey sKey = sKR.getSecretKey(strip);
|
||||
if (sKey == null) {
|
||||
log.add(LogType.MSG_MF_ERROR_SUBKEY_MISSING,
|
||||
indent+1, KeyFormattingUtils.convertKeyIdToHex(strip));
|
||||
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
// IT'S DANGEROUS~
|
||||
// no really, it is. this operation irrevocably removes the private key data from the key
|
||||
sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey());
|
||||
sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
|
||||
|
||||
}
|
||||
subProgressPop();
|
||||
|
||||
// 5. Generate and add new subkeys
|
||||
subProgressPush(70, 90);
|
||||
for (int i = 0; i < saveParcel.mAddSubKeys.size(); i++) {
|
||||
@@ -937,6 +940,73 @@ public class PgpKeyOperation {
|
||||
|
||||
}
|
||||
|
||||
/** This method does the actual modifications in a keyring just like internal, except it
|
||||
* supports only the subset of operations which require no passphrase, and will error
|
||||
* otherwise.
|
||||
*/
|
||||
private PgpEditKeyResult internalRestricted(PGPSecretKeyRing sKR, SaveKeyringParcel saveParcel,
|
||||
OperationLog log) {
|
||||
|
||||
int indent = 1;
|
||||
|
||||
progress(R.string.progress_modify, 0);
|
||||
|
||||
// Make sure the saveParcel includes only operations available without passphrae!
|
||||
if (!saveParcel.isRestrictedOnly()) {
|
||||
log.add(LogType.MSG_MF_ERROR_RESTRICTED, indent);
|
||||
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
// Check if we were cancelled
|
||||
if (checkCancelled()) {
|
||||
log.add(LogType.MSG_OPERATION_CANCELLED, indent);
|
||||
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_CANCELLED, log, null);
|
||||
}
|
||||
|
||||
// The only operation we can do here:
|
||||
// 4a. Strip secret keys, or change their protection mode (stripped/divert-to-card)
|
||||
subProgressPush(50, 60);
|
||||
for (int i = 0; i < saveParcel.mChangeSubKeys.size(); i++) {
|
||||
|
||||
progress(R.string.progress_modify_subkeychange, (i - 1) * (100 / saveParcel.mChangeSubKeys.size()));
|
||||
SaveKeyringParcel.SubkeyChange change = saveParcel.mChangeSubKeys.get(i);
|
||||
log.add(LogType.MSG_MF_SUBKEY_CHANGE,
|
||||
indent, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
|
||||
|
||||
PGPSecretKey sKey = sKR.getSecretKey(change.mKeyId);
|
||||
if (sKey == null) {
|
||||
log.add(LogType.MSG_MF_ERROR_SUBKEY_MISSING,
|
||||
indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
|
||||
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
|
||||
if (change.mDummyStrip || change.mDummyDivert != null) {
|
||||
// IT'S DANGEROUS~
|
||||
// no really, it is. this operation irrevocably removes the private key data from the key
|
||||
if (change.mDummyStrip) {
|
||||
sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey());
|
||||
} else {
|
||||
// the serial number must be 16 bytes in length
|
||||
if (change.mDummyDivert.length != 16) {
|
||||
log.add(LogType.MSG_MF_ERROR_DIVERT_SERIAL,
|
||||
indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
|
||||
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
||||
}
|
||||
sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey(), change.mDummyDivert);
|
||||
}
|
||||
sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// And we're done!
|
||||
progress(R.string.progress_done, 100);
|
||||
log.add(LogType.MSG_MF_SUCCESS, indent);
|
||||
return new PgpEditKeyResult(OperationResult.RESULT_OK, log, new UncachedKeyRing(sKR));
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static PGPSecretKeyRing applyNewUnlock(
|
||||
PGPSecretKeyRing sKR,
|
||||
PGPPublicKey masterPublicKey,
|
||||
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import org.spongycastle.bcpg.CompressionAlgorithmTags;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class PgpSignEncryptInput {
|
||||
|
||||
protected String mVersionHeader = null;
|
||||
protected boolean mEnableAsciiArmorOutput = false;
|
||||
protected int mCompressionId = CompressionAlgorithmTags.UNCOMPRESSED;
|
||||
protected long[] mEncryptionMasterKeyIds = null;
|
||||
protected String mSymmetricPassphrase = null;
|
||||
protected int mSymmetricEncryptionAlgorithm = 0;
|
||||
protected long mSignatureMasterKeyId = Constants.key.none;
|
||||
protected Long mSignatureSubKeyId = null;
|
||||
protected int mSignatureHashAlgorithm = 0;
|
||||
protected String mSignaturePassphrase = null;
|
||||
protected long mAdditionalEncryptId = Constants.key.none;
|
||||
protected byte[] mNfcSignedHash = null;
|
||||
protected Date mNfcCreationTimestamp = null;
|
||||
protected boolean mFailOnMissingEncryptionKeyIds = false;
|
||||
protected String mCharset;
|
||||
protected boolean mCleartextSignature;
|
||||
protected boolean mDetachedSignature;
|
||||
|
||||
public String getCharset() {
|
||||
return mCharset;
|
||||
}
|
||||
|
||||
public void setCharset(String mCharset) {
|
||||
this.mCharset = mCharset;
|
||||
}
|
||||
|
||||
public boolean ismFailOnMissingEncryptionKeyIds() {
|
||||
return mFailOnMissingEncryptionKeyIds;
|
||||
}
|
||||
|
||||
public Date getNfcCreationTimestamp() {
|
||||
return mNfcCreationTimestamp;
|
||||
}
|
||||
|
||||
public byte[] getNfcSignedHash() {
|
||||
return mNfcSignedHash;
|
||||
}
|
||||
|
||||
public long getAdditionalEncryptId() {
|
||||
return mAdditionalEncryptId;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setAdditionalEncryptId(long additionalEncryptId) {
|
||||
mAdditionalEncryptId = additionalEncryptId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getSignaturePassphrase() {
|
||||
return mSignaturePassphrase;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setSignaturePassphrase(String signaturePassphrase) {
|
||||
mSignaturePassphrase = signaturePassphrase;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getSignatureHashAlgorithm() {
|
||||
return mSignatureHashAlgorithm;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setSignatureHashAlgorithm(int signatureHashAlgorithm) {
|
||||
mSignatureHashAlgorithm = signatureHashAlgorithm;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Long getSignatureSubKeyId() {
|
||||
return mSignatureSubKeyId;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setSignatureSubKeyId(long signatureSubKeyId) {
|
||||
mSignatureSubKeyId = signatureSubKeyId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public long getSignatureMasterKeyId() {
|
||||
return mSignatureMasterKeyId;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setSignatureMasterKeyId(long signatureMasterKeyId) {
|
||||
mSignatureMasterKeyId = signatureMasterKeyId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getSymmetricEncryptionAlgorithm() {
|
||||
return mSymmetricEncryptionAlgorithm;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setSymmetricEncryptionAlgorithm(int symmetricEncryptionAlgorithm) {
|
||||
mSymmetricEncryptionAlgorithm = symmetricEncryptionAlgorithm;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getSymmetricPassphrase() {
|
||||
return mSymmetricPassphrase;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setSymmetricPassphrase(String symmetricPassphrase) {
|
||||
mSymmetricPassphrase = symmetricPassphrase;
|
||||
return this;
|
||||
}
|
||||
|
||||
public long[] getEncryptionMasterKeyIds() {
|
||||
return mEncryptionMasterKeyIds;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setEncryptionMasterKeyIds(long[] encryptionMasterKeyIds) {
|
||||
mEncryptionMasterKeyIds = encryptionMasterKeyIds;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getCompressionId() {
|
||||
return mCompressionId;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setCompressionId(int compressionId) {
|
||||
mCompressionId = compressionId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean ismEnableAsciiArmorOutput() {
|
||||
return mEnableAsciiArmorOutput;
|
||||
}
|
||||
|
||||
public String getVersionHeader() {
|
||||
return mVersionHeader;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setVersionHeader(String versionHeader) {
|
||||
mVersionHeader = versionHeader;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setEnableAsciiArmorOutput(boolean enableAsciiArmorOutput) {
|
||||
mEnableAsciiArmorOutput = enableAsciiArmorOutput;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setFailOnMissingEncryptionKeyIds(boolean failOnMissingEncryptionKeyIds) {
|
||||
mFailOnMissingEncryptionKeyIds = failOnMissingEncryptionKeyIds;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setNfcState(byte[] signedHash, Date creationTimestamp) {
|
||||
mNfcSignedHash = signedHash;
|
||||
mNfcCreationTimestamp = creationTimestamp;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setCleartextSignature(boolean cleartextSignature) {
|
||||
this.mCleartextSignature = cleartextSignature;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isCleartextSignature() {
|
||||
return mCleartextSignature;
|
||||
}
|
||||
|
||||
public PgpSignEncryptInput setDetachedSignature(boolean detachedSignature) {
|
||||
this.mDetachedSignature = detachedSignature;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isDetachedSignature() {
|
||||
return mDetachedSignature;
|
||||
}
|
||||
}
|
||||
|
||||
+213
-275
@@ -25,6 +25,7 @@ import org.spongycastle.bcpg.ArmoredOutputStream;
|
||||
import org.spongycastle.bcpg.BCPGOutputStream;
|
||||
import org.spongycastle.bcpg.CompressionAlgorithmTags;
|
||||
import org.spongycastle.openpgp.PGPCompressedDataGenerator;
|
||||
import org.spongycastle.openpgp.PGPEncryptedData;
|
||||
import org.spongycastle.openpgp.PGPEncryptedDataGenerator;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPLiteralData;
|
||||
@@ -37,19 +38,20 @@ import org.spongycastle.util.encoders.Hex;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.BaseOperation;
|
||||
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.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
@@ -59,31 +61,23 @@ import java.security.SignatureException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* This class uses a Builder pattern!
|
||||
/** This class supports a single, low-level, sign/encrypt operation.
|
||||
*
|
||||
* The operation of this class takes an Input- and OutputStream plus a
|
||||
* PgpSignEncryptInput, and signs and/or encrypts the stream as
|
||||
* parametrized in the PgpSignEncryptInput object. It returns its status
|
||||
* and a possible detached signature as a SignEncryptResult.
|
||||
*
|
||||
* For a high-level operation based on URIs, see SignEncryptOperation.
|
||||
*
|
||||
* @see org.sufficientlysecure.keychain.pgp.PgpSignEncryptInput
|
||||
* @see org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult
|
||||
* @see org.sufficientlysecure.keychain.operations.SignEncryptOperation
|
||||
*
|
||||
*/
|
||||
public class PgpSignEncrypt extends BaseOperation {
|
||||
private String mVersionHeader;
|
||||
private InputData mData;
|
||||
private OutputStream mOutStream;
|
||||
|
||||
private boolean mEnableAsciiArmorOutput;
|
||||
private int mCompressionId;
|
||||
private long[] mEncryptionMasterKeyIds;
|
||||
private String mSymmetricPassphrase;
|
||||
private int mSymmetricEncryptionAlgorithm;
|
||||
private long mSignatureMasterKeyId;
|
||||
private Long mSignatureSubKeyId;
|
||||
private int mSignatureHashAlgorithm;
|
||||
private String mSignaturePassphrase;
|
||||
private long mAdditionalEncryptId;
|
||||
private boolean mCleartextInput;
|
||||
private String mOriginalFilename;
|
||||
private boolean mFailOnMissingEncryptionKeyIds;
|
||||
|
||||
private byte[] mNfcSignedHash = null;
|
||||
private Date mNfcCreationTimestamp = null;
|
||||
public class PgpSignEncryptOperation extends BaseOperation {
|
||||
|
||||
private static byte[] NEW_LINE;
|
||||
|
||||
@@ -95,241 +89,107 @@ public class PgpSignEncrypt extends BaseOperation {
|
||||
}
|
||||
}
|
||||
|
||||
protected PgpSignEncrypt(Builder builder) {
|
||||
super(builder.mContext, builder.mProviderHelper, builder.mProgressable);
|
||||
|
||||
// private Constructor can only be called from Builder
|
||||
this.mVersionHeader = builder.mVersionHeader;
|
||||
this.mData = builder.mData;
|
||||
this.mOutStream = builder.mOutStream;
|
||||
|
||||
this.mEnableAsciiArmorOutput = builder.mEnableAsciiArmorOutput;
|
||||
this.mCompressionId = builder.mCompressionId;
|
||||
this.mEncryptionMasterKeyIds = builder.mEncryptionMasterKeyIds;
|
||||
this.mSymmetricPassphrase = builder.mSymmetricPassphrase;
|
||||
this.mSymmetricEncryptionAlgorithm = builder.mSymmetricEncryptionAlgorithm;
|
||||
this.mSignatureMasterKeyId = builder.mSignatureMasterKeyId;
|
||||
this.mSignatureSubKeyId = builder.mSignatureSubKeyId;
|
||||
this.mSignatureHashAlgorithm = builder.mSignatureHashAlgorithm;
|
||||
this.mSignaturePassphrase = builder.mSignaturePassphrase;
|
||||
this.mAdditionalEncryptId = builder.mAdditionalEncryptId;
|
||||
this.mCleartextInput = builder.mCleartextInput;
|
||||
this.mNfcSignedHash = builder.mNfcSignedHash;
|
||||
this.mNfcCreationTimestamp = builder.mNfcCreationTimestamp;
|
||||
this.mOriginalFilename = builder.mOriginalFilename;
|
||||
this.mFailOnMissingEncryptionKeyIds = builder.mFailOnMissingEncryptionKeyIds;
|
||||
public PgpSignEncryptOperation(Context context, ProviderHelper providerHelper, Progressable progressable, AtomicBoolean cancelled) {
|
||||
super(context, providerHelper, progressable, cancelled);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
// mandatory parameter
|
||||
private Context mContext;
|
||||
private ProviderHelper mProviderHelper;
|
||||
private Progressable mProgressable;
|
||||
private InputData mData;
|
||||
private OutputStream mOutStream;
|
||||
|
||||
// optional
|
||||
private String mVersionHeader = null;
|
||||
private boolean mEnableAsciiArmorOutput = false;
|
||||
private int mCompressionId = CompressionAlgorithmTags.UNCOMPRESSED;
|
||||
private long[] mEncryptionMasterKeyIds = null;
|
||||
private String mSymmetricPassphrase = null;
|
||||
private int mSymmetricEncryptionAlgorithm = 0;
|
||||
private long mSignatureMasterKeyId = Constants.key.none;
|
||||
private Long mSignatureSubKeyId = null;
|
||||
private int mSignatureHashAlgorithm = 0;
|
||||
private String mSignaturePassphrase = null;
|
||||
private long mAdditionalEncryptId = Constants.key.none;
|
||||
private boolean mCleartextInput = false;
|
||||
private String mOriginalFilename = "";
|
||||
private byte[] mNfcSignedHash = null;
|
||||
private Date mNfcCreationTimestamp = null;
|
||||
private boolean mFailOnMissingEncryptionKeyIds = false;
|
||||
|
||||
public Builder(Context context, ProviderHelper providerHelper, Progressable progressable,
|
||||
InputData data, OutputStream outStream) {
|
||||
mContext = context;
|
||||
mProviderHelper = providerHelper;
|
||||
mProgressable = progressable;
|
||||
|
||||
mData = data;
|
||||
mOutStream = outStream;
|
||||
}
|
||||
|
||||
public Builder setVersionHeader(String versionHeader) {
|
||||
mVersionHeader = versionHeader;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setEnableAsciiArmorOutput(boolean enableAsciiArmorOutput) {
|
||||
mEnableAsciiArmorOutput = enableAsciiArmorOutput;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setCompressionId(int compressionId) {
|
||||
mCompressionId = compressionId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setEncryptionMasterKeyIds(long[] encryptionMasterKeyIds) {
|
||||
mEncryptionMasterKeyIds = encryptionMasterKeyIds;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setSymmetricPassphrase(String symmetricPassphrase) {
|
||||
mSymmetricPassphrase = symmetricPassphrase;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setSymmetricEncryptionAlgorithm(int symmetricEncryptionAlgorithm) {
|
||||
mSymmetricEncryptionAlgorithm = symmetricEncryptionAlgorithm;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setSignatureMasterKeyId(long signatureMasterKeyId) {
|
||||
mSignatureMasterKeyId = signatureMasterKeyId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setSignatureSubKeyId(long signatureSubKeyId) {
|
||||
mSignatureSubKeyId = signatureSubKeyId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setSignatureHashAlgorithm(int signatureHashAlgorithm) {
|
||||
mSignatureHashAlgorithm = signatureHashAlgorithm;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setSignaturePassphrase(String signaturePassphrase) {
|
||||
mSignaturePassphrase = signaturePassphrase;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setFailOnMissingEncryptionKeyIds(boolean failOnMissingEncryptionKeyIds) {
|
||||
mFailOnMissingEncryptionKeyIds = failOnMissingEncryptionKeyIds;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Also encrypt with the signing keyring
|
||||
*
|
||||
* @param additionalEncryptId
|
||||
* @return
|
||||
*/
|
||||
public Builder setAdditionalEncryptId(long additionalEncryptId) {
|
||||
mAdditionalEncryptId = additionalEncryptId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: test this option!
|
||||
*
|
||||
* @param cleartextInput
|
||||
* @return
|
||||
*/
|
||||
public Builder setCleartextInput(boolean cleartextInput) {
|
||||
mCleartextInput = cleartextInput;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setOriginalFilename(String originalFilename) {
|
||||
mOriginalFilename = originalFilename;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setNfcState(byte[] signedHash, Date creationTimestamp) {
|
||||
mNfcSignedHash = signedHash;
|
||||
mNfcCreationTimestamp = creationTimestamp;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PgpSignEncrypt build() {
|
||||
return new PgpSignEncrypt(this);
|
||||
}
|
||||
public PgpSignEncryptOperation(Context context, ProviderHelper providerHelper, Progressable progressable) {
|
||||
super(context, providerHelper, progressable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs and/or encrypts data based on parameters of class
|
||||
*/
|
||||
public SignEncryptResult execute() {
|
||||
public PgpSignEncryptResult execute(PgpSignEncryptInput input,
|
||||
InputData inputData, OutputStream outputStream) {
|
||||
|
||||
int indent = 0;
|
||||
OperationLog log = new OperationLog();
|
||||
|
||||
log.add(LogType.MSG_SE, indent);
|
||||
log.add(LogType.MSG_PSE, indent);
|
||||
indent += 1;
|
||||
|
||||
boolean enableSignature = mSignatureMasterKeyId != Constants.key.none;
|
||||
boolean enableEncryption = ((mEncryptionMasterKeyIds != null && mEncryptionMasterKeyIds.length > 0)
|
||||
|| mSymmetricPassphrase != null);
|
||||
boolean enableCompression = (mCompressionId != CompressionAlgorithmTags.UNCOMPRESSED);
|
||||
boolean enableSignature = input.getSignatureMasterKeyId() != Constants.key.none;
|
||||
boolean enableEncryption = ((input.getEncryptionMasterKeyIds() != null && input.getEncryptionMasterKeyIds().length > 0)
|
||||
|| input.getSymmetricPassphrase() != null);
|
||||
boolean enableCompression = (input.getCompressionId() != CompressionAlgorithmTags.UNCOMPRESSED);
|
||||
|
||||
Log.d(Constants.TAG, "enableSignature:" + enableSignature
|
||||
+ "\nenableEncryption:" + enableEncryption
|
||||
+ "\nenableCompression:" + enableCompression
|
||||
+ "\nenableAsciiArmorOutput:" + mEnableAsciiArmorOutput);
|
||||
+ "\nenableAsciiArmorOutput:" + input.ismEnableAsciiArmorOutput());
|
||||
|
||||
// add additional key id to encryption ids (mostly to do self-encryption)
|
||||
if (enableEncryption && mAdditionalEncryptId != Constants.key.none) {
|
||||
mEncryptionMasterKeyIds = Arrays.copyOf(mEncryptionMasterKeyIds, mEncryptionMasterKeyIds.length + 1);
|
||||
mEncryptionMasterKeyIds[mEncryptionMasterKeyIds.length - 1] = mAdditionalEncryptId;
|
||||
if (enableEncryption && input.getAdditionalEncryptId() != Constants.key.none) {
|
||||
input.setEncryptionMasterKeyIds(Arrays.copyOf(input.getEncryptionMasterKeyIds(), input.getEncryptionMasterKeyIds().length + 1));
|
||||
input.getEncryptionMasterKeyIds()[input.getEncryptionMasterKeyIds().length - 1] = input.getAdditionalEncryptId();
|
||||
}
|
||||
|
||||
ArmoredOutputStream armorOut = null;
|
||||
OutputStream out;
|
||||
if (mEnableAsciiArmorOutput) {
|
||||
armorOut = new ArmoredOutputStream(mOutStream);
|
||||
if (mVersionHeader != null) {
|
||||
armorOut.setHeader("Version", mVersionHeader);
|
||||
if (input.ismEnableAsciiArmorOutput()) {
|
||||
armorOut = new ArmoredOutputStream(outputStream);
|
||||
if (input.getVersionHeader() != null) {
|
||||
armorOut.setHeader("Version", input.getVersionHeader());
|
||||
}
|
||||
// if we have a charset, put it in the header
|
||||
if (input.getCharset() != null) {
|
||||
armorOut.setHeader("Charset", input.getCharset());
|
||||
}
|
||||
out = armorOut;
|
||||
} else {
|
||||
out = mOutStream;
|
||||
out = outputStream;
|
||||
}
|
||||
|
||||
/* Get keys for signature generation for later usage */
|
||||
CanonicalizedSecretKey signingKey = null;
|
||||
long signKeyId;
|
||||
if (enableSignature) {
|
||||
|
||||
try {
|
||||
// fetch the indicated master key id (the one whose name we sign in)
|
||||
CanonicalizedSecretKeyRing signingKeyRing =
|
||||
mProviderHelper.getCanonicalizedSecretKeyRing(mSignatureMasterKeyId);
|
||||
mProviderHelper.getCanonicalizedSecretKeyRing(input.getSignatureMasterKeyId());
|
||||
|
||||
long signKeyId;
|
||||
// use specified signing subkey, or find the one to use
|
||||
if (input.getSignatureSubKeyId() == null) {
|
||||
signKeyId = signingKeyRing.getSecretSignId();
|
||||
} else {
|
||||
signKeyId = input.getSignatureSubKeyId();
|
||||
}
|
||||
|
||||
// fetch the specific subkey to sign with, or just use the master key if none specified
|
||||
signKeyId = mSignatureSubKeyId != null ? mSignatureSubKeyId : mSignatureMasterKeyId;
|
||||
signingKey = signingKeyRing.getSecretKey(signKeyId);
|
||||
// make sure it's a signing key alright!
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
log.add(LogType.MSG_SE_ERROR_SIGN_KEY, indent);
|
||||
return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log);
|
||||
|
||||
} catch (ProviderHelper.NotFoundException | PgpGeneralException e) {
|
||||
log.add(LogType.MSG_PSE_ERROR_SIGN_KEY, indent);
|
||||
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
// Make sure we are allowed to sign here!
|
||||
if (!signingKey.canSign()) {
|
||||
log.add(LogType.MSG_SE_ERROR_KEY_SIGN, indent);
|
||||
return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log);
|
||||
log.add(LogType.MSG_PSE_ERROR_KEY_SIGN, indent);
|
||||
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
// if no passphrase was explicitly set try to get it from the cache service
|
||||
if (mSignaturePassphrase == null) {
|
||||
if (input.getSignaturePassphrase() == null) {
|
||||
try {
|
||||
// returns "" if key has no passphrase
|
||||
mSignaturePassphrase = getCachedPassphrase(signKeyId);
|
||||
input.setSignaturePassphrase(getCachedPassphrase(signingKey.getKeyId()));
|
||||
// TODO
|
||||
// log.add(LogType.MSG_DC_PASS_CACHED, indent + 1);
|
||||
} catch (PassphraseCacheInterface.NoSecretKeyException e) {
|
||||
// TODO
|
||||
// log.add(LogType.MSG_DC_ERROR_NO_KEY, indent + 1);
|
||||
return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log);
|
||||
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
// if passphrase was not cached, return here indicating that a passphrase is missing!
|
||||
if (mSignaturePassphrase == null) {
|
||||
log.add(LogType.MSG_SE_PENDING_PASSPHRASE, indent + 1);
|
||||
SignEncryptResult result = new SignEncryptResult(SignEncryptResult.RESULT_PENDING_PASSPHRASE, log);
|
||||
result.setKeyIdPassphraseNeeded(signKeyId);
|
||||
if (input.getSignaturePassphrase() == null) {
|
||||
log.add(LogType.MSG_PSE_PENDING_PASSPHRASE, indent + 1);
|
||||
PgpSignEncryptResult result = new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE, log);
|
||||
result.setKeyIdPassphraseNeeded(signingKey.getKeyId());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -337,20 +197,24 @@ public class PgpSignEncrypt extends BaseOperation {
|
||||
updateProgress(R.string.progress_extracting_signature_key, 0, 100);
|
||||
|
||||
try {
|
||||
if (!signingKey.unlock(mSignaturePassphrase)) {
|
||||
log.add(LogType.MSG_SE_ERROR_BAD_PASSPHRASE, indent);
|
||||
return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log);
|
||||
if (!signingKey.unlock(input.getSignaturePassphrase())) {
|
||||
log.add(LogType.MSG_PSE_ERROR_BAD_PASSPHRASE, indent);
|
||||
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||
}
|
||||
} catch (PgpGeneralException e) {
|
||||
log.add(LogType.MSG_SE_ERROR_UNLOCK, indent);
|
||||
return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log);
|
||||
log.add(LogType.MSG_PSE_ERROR_UNLOCK, indent);
|
||||
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
// check if hash algo is supported
|
||||
int requestedAlgorithm = input.getSignatureHashAlgorithm();
|
||||
LinkedList<Integer> supported = signingKey.getSupportedHashAlgorithms();
|
||||
if (!supported.contains(mSignatureHashAlgorithm)) {
|
||||
if (requestedAlgorithm == 0) {
|
||||
// get most preferred
|
||||
mSignatureHashAlgorithm = supported.getLast();
|
||||
input.setSignatureHashAlgorithm(supported.getLast());
|
||||
} else if (!supported.contains(requestedAlgorithm)) {
|
||||
log.add(LogType.MSG_PSE_ERROR_HASH_ALGO, indent);
|
||||
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||
}
|
||||
}
|
||||
updateProgress(R.string.progress_preparing_streams, 2, 100);
|
||||
@@ -358,44 +222,48 @@ public class PgpSignEncrypt extends BaseOperation {
|
||||
/* Initialize PGPEncryptedDataGenerator for later usage */
|
||||
PGPEncryptedDataGenerator cPk = null;
|
||||
if (enableEncryption) {
|
||||
int algo = input.getSymmetricEncryptionAlgorithm();
|
||||
if (algo == 0) {
|
||||
algo = PGPEncryptedData.AES_128;
|
||||
}
|
||||
// has Integrity packet enabled!
|
||||
JcePGPDataEncryptorBuilder encryptorBuilder =
|
||||
new JcePGPDataEncryptorBuilder(mSymmetricEncryptionAlgorithm)
|
||||
new JcePGPDataEncryptorBuilder(algo)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME)
|
||||
.setWithIntegrityPacket(true);
|
||||
|
||||
cPk = new PGPEncryptedDataGenerator(encryptorBuilder);
|
||||
|
||||
if (mSymmetricPassphrase != null) {
|
||||
if (input.getSymmetricPassphrase() != null) {
|
||||
// Symmetric encryption
|
||||
log.add(LogType.MSG_SE_SYMMETRIC, indent);
|
||||
log.add(LogType.MSG_PSE_SYMMETRIC, indent);
|
||||
|
||||
JcePBEKeyEncryptionMethodGenerator symmetricEncryptionGenerator =
|
||||
new JcePBEKeyEncryptionMethodGenerator(mSymmetricPassphrase.toCharArray());
|
||||
new JcePBEKeyEncryptionMethodGenerator(input.getSymmetricPassphrase().toCharArray());
|
||||
cPk.addMethod(symmetricEncryptionGenerator);
|
||||
} else {
|
||||
log.add(LogType.MSG_SE_ASYMMETRIC, indent);
|
||||
log.add(LogType.MSG_PSE_ASYMMETRIC, indent);
|
||||
|
||||
// Asymmetric encryption
|
||||
for (long id : mEncryptionMasterKeyIds) {
|
||||
for (long id : input.getEncryptionMasterKeyIds()) {
|
||||
try {
|
||||
CanonicalizedPublicKeyRing keyRing = mProviderHelper.getCanonicalizedPublicKeyRing(
|
||||
KeyRings.buildUnifiedKeyRingUri(id));
|
||||
CanonicalizedPublicKey key = keyRing.getEncryptionSubKey();
|
||||
cPk.addMethod(key.getPubKeyEncryptionGenerator());
|
||||
log.add(LogType.MSG_SE_KEY_OK, indent + 1,
|
||||
log.add(LogType.MSG_PSE_KEY_OK, indent + 1,
|
||||
KeyFormattingUtils.convertKeyIdToHex(id));
|
||||
} catch (PgpKeyNotFoundException e) {
|
||||
log.add(LogType.MSG_SE_KEY_WARN, indent + 1,
|
||||
log.add(LogType.MSG_PSE_KEY_WARN, indent + 1,
|
||||
KeyFormattingUtils.convertKeyIdToHex(id));
|
||||
if (mFailOnMissingEncryptionKeyIds) {
|
||||
return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log);
|
||||
if (input.ismFailOnMissingEncryptionKeyIds()) {
|
||||
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||
}
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
log.add(LogType.MSG_SE_KEY_UNKNOWN, indent + 1,
|
||||
log.add(LogType.MSG_PSE_KEY_UNKNOWN, indent + 1,
|
||||
KeyFormattingUtils.convertKeyIdToHex(id));
|
||||
if (mFailOnMissingEncryptionKeyIds) {
|
||||
return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log);
|
||||
if (input.ismFailOnMissingEncryptionKeyIds()) {
|
||||
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -408,12 +276,12 @@ public class PgpSignEncrypt extends BaseOperation {
|
||||
updateProgress(R.string.progress_preparing_signature, 4, 100);
|
||||
|
||||
try {
|
||||
boolean cleartext = mCleartextInput && mEnableAsciiArmorOutput && !enableEncryption;
|
||||
boolean cleartext = input.isCleartextSignature() && input.ismEnableAsciiArmorOutput() && !enableEncryption;
|
||||
signatureGenerator = signingKey.getSignatureGenerator(
|
||||
mSignatureHashAlgorithm, cleartext, mNfcSignedHash, mNfcCreationTimestamp);
|
||||
input.getSignatureHashAlgorithm(), cleartext, input.getNfcSignedHash(), input.getNfcCreationTimestamp());
|
||||
} catch (PgpGeneralException e) {
|
||||
log.add(LogType.MSG_SE_ERROR_NFC, indent);
|
||||
return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log);
|
||||
log.add(LogType.MSG_PSE_ERROR_NFC, indent);
|
||||
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -424,14 +292,18 @@ public class PgpSignEncrypt extends BaseOperation {
|
||||
OutputStream encryptionOut = null;
|
||||
BCPGOutputStream bcpgOut;
|
||||
|
||||
ByteArrayOutputStream detachedByteOut = null;
|
||||
ArmoredOutputStream detachedArmorOut = null;
|
||||
BCPGOutputStream detachedBcpgOut = null;
|
||||
|
||||
try {
|
||||
|
||||
if (enableEncryption) {
|
||||
/* actual encryption */
|
||||
updateProgress(R.string.progress_encrypting, 8, 100);
|
||||
log.add(enableSignature
|
||||
? LogType.MSG_SE_SIGCRYPTING
|
||||
: LogType.MSG_SE_ENCRYPTING,
|
||||
? LogType.MSG_PSE_SIGCRYPTING
|
||||
: LogType.MSG_PSE_ENCRYPTING,
|
||||
indent
|
||||
);
|
||||
indent += 1;
|
||||
@@ -439,8 +311,8 @@ public class PgpSignEncrypt extends BaseOperation {
|
||||
encryptionOut = cPk.open(out, new byte[1 << 16]);
|
||||
|
||||
if (enableCompression) {
|
||||
log.add(LogType.MSG_SE_COMPRESSING, indent);
|
||||
compressGen = new PGPCompressedDataGenerator(mCompressionId);
|
||||
log.add(LogType.MSG_PSE_COMPRESSING, indent);
|
||||
compressGen = new PGPCompressedDataGenerator(input.getCompressionId());
|
||||
bcpgOut = new BCPGOutputStream(compressGen.open(encryptionOut));
|
||||
} else {
|
||||
bcpgOut = new BCPGOutputStream(encryptionOut);
|
||||
@@ -452,18 +324,18 @@ public class PgpSignEncrypt extends BaseOperation {
|
||||
|
||||
PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator();
|
||||
char literalDataFormatTag;
|
||||
if (mCleartextInput) {
|
||||
if (input.isCleartextSignature()) {
|
||||
literalDataFormatTag = PGPLiteralData.UTF8;
|
||||
} else {
|
||||
literalDataFormatTag = PGPLiteralData.BINARY;
|
||||
}
|
||||
pOut = literalGen.open(bcpgOut, literalDataFormatTag, mOriginalFilename, new Date(),
|
||||
new byte[1 << 16]);
|
||||
pOut = literalGen.open(bcpgOut, literalDataFormatTag,
|
||||
inputData.getOriginalFilename(), new Date(), new byte[1 << 16]);
|
||||
|
||||
long alreadyWritten = 0;
|
||||
int length;
|
||||
byte[] buffer = new byte[1 << 16];
|
||||
InputStream in = mData.getInputStream();
|
||||
InputStream in = inputData.getInputStream();
|
||||
while ((length = in.read(buffer)) > 0) {
|
||||
pOut.write(buffer, 0, length);
|
||||
|
||||
@@ -473,8 +345,8 @@ public class PgpSignEncrypt extends BaseOperation {
|
||||
}
|
||||
|
||||
alreadyWritten += length;
|
||||
if (mData.getSize() > 0) {
|
||||
long progress = 100 * alreadyWritten / mData.getSize();
|
||||
if (inputData.getSize() > 0) {
|
||||
long progress = 100 * alreadyWritten / inputData.getSize();
|
||||
progressScaler.setProgress((int) progress, 100);
|
||||
}
|
||||
}
|
||||
@@ -482,16 +354,16 @@ public class PgpSignEncrypt extends BaseOperation {
|
||||
literalGen.close();
|
||||
indent -= 1;
|
||||
|
||||
} else if (enableSignature && mCleartextInput && mEnableAsciiArmorOutput) {
|
||||
} else if (enableSignature && input.isCleartextSignature() && input.ismEnableAsciiArmorOutput()) {
|
||||
/* cleartext signature: sign-only of ascii text */
|
||||
|
||||
updateProgress(R.string.progress_signing, 8, 100);
|
||||
log.add(LogType.MSG_SE_SIGNING, indent);
|
||||
log.add(LogType.MSG_PSE_SIGNING_CLEARTEXT, indent);
|
||||
|
||||
// write -----BEGIN PGP SIGNED MESSAGE-----
|
||||
armorOut.beginClearText(mSignatureHashAlgorithm);
|
||||
armorOut.beginClearText(input.getSignatureHashAlgorithm());
|
||||
|
||||
InputStream in = mData.getInputStream();
|
||||
InputStream in = inputData.getInputStream();
|
||||
final BufferedReader reader = new BufferedReader(new InputStreamReader(in));
|
||||
|
||||
// update signature buffer with first line
|
||||
@@ -517,16 +389,53 @@ public class PgpSignEncrypt extends BaseOperation {
|
||||
armorOut.endClearText();
|
||||
|
||||
pOut = new BCPGOutputStream(armorOut);
|
||||
} else if (enableSignature && !mCleartextInput) {
|
||||
} else if (enableSignature && input.isDetachedSignature()) {
|
||||
/* detached signature */
|
||||
|
||||
updateProgress(R.string.progress_signing, 8, 100);
|
||||
log.add(LogType.MSG_PSE_SIGNING_DETACHED, indent);
|
||||
|
||||
InputStream in = inputData.getInputStream();
|
||||
|
||||
// handle output stream separately for detached signatures
|
||||
detachedByteOut = new ByteArrayOutputStream();
|
||||
OutputStream detachedOut = detachedByteOut;
|
||||
if (input.ismEnableAsciiArmorOutput()) {
|
||||
detachedArmorOut = new ArmoredOutputStream(detachedOut);
|
||||
if (input.getVersionHeader() != null) {
|
||||
detachedArmorOut.setHeader("Version", input.getVersionHeader());
|
||||
}
|
||||
|
||||
detachedOut = detachedArmorOut;
|
||||
}
|
||||
detachedBcpgOut = new BCPGOutputStream(detachedOut);
|
||||
|
||||
long alreadyWritten = 0;
|
||||
int length;
|
||||
byte[] buffer = new byte[1 << 16];
|
||||
while ((length = in.read(buffer)) > 0) {
|
||||
// no output stream is written, no changed to original data!
|
||||
|
||||
signatureGenerator.update(buffer, 0, length);
|
||||
|
||||
alreadyWritten += length;
|
||||
if (inputData.getSize() > 0) {
|
||||
long progress = 100 * alreadyWritten / inputData.getSize();
|
||||
progressScaler.setProgress((int) progress, 100);
|
||||
}
|
||||
}
|
||||
|
||||
pOut = null;
|
||||
} else if (enableSignature && !input.isCleartextSignature() && !input.isDetachedSignature()) {
|
||||
/* sign-only binary (files/data stream) */
|
||||
|
||||
updateProgress(R.string.progress_signing, 8, 100);
|
||||
log.add(LogType.MSG_SE_ENCRYPTING, indent);
|
||||
log.add(LogType.MSG_PSE_SIGNING, indent);
|
||||
|
||||
InputStream in = mData.getInputStream();
|
||||
InputStream in = inputData.getInputStream();
|
||||
|
||||
if (enableCompression) {
|
||||
compressGen = new PGPCompressedDataGenerator(mCompressionId);
|
||||
compressGen = new PGPCompressedDataGenerator(input.getCompressionId());
|
||||
bcpgOut = new BCPGOutputStream(compressGen.open(out));
|
||||
} else {
|
||||
bcpgOut = new BCPGOutputStream(out);
|
||||
@@ -535,7 +444,8 @@ public class PgpSignEncrypt extends BaseOperation {
|
||||
signatureGenerator.generateOnePassVersion(false).encode(bcpgOut);
|
||||
|
||||
PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator();
|
||||
pOut = literalGen.open(bcpgOut, PGPLiteralData.BINARY, mOriginalFilename, new Date(),
|
||||
pOut = literalGen.open(bcpgOut, PGPLiteralData.BINARY,
|
||||
inputData.getOriginalFilename(), new Date(),
|
||||
new byte[1 << 16]);
|
||||
|
||||
long alreadyWritten = 0;
|
||||
@@ -547,8 +457,8 @@ public class PgpSignEncrypt extends BaseOperation {
|
||||
signatureGenerator.update(buffer, 0, length);
|
||||
|
||||
alreadyWritten += length;
|
||||
if (mData.getSize() > 0) {
|
||||
long progress = 100 * alreadyWritten / mData.getSize();
|
||||
if (inputData.getSize() > 0) {
|
||||
long progress = 100 * alreadyWritten / inputData.getSize();
|
||||
progressScaler.setProgress((int) progress, 100);
|
||||
}
|
||||
}
|
||||
@@ -556,61 +466,89 @@ public class PgpSignEncrypt extends BaseOperation {
|
||||
literalGen.close();
|
||||
} else {
|
||||
pOut = null;
|
||||
log.add(LogType.MSG_SE_CLEARSIGN_ONLY, indent);
|
||||
// TODO: Is this log right?
|
||||
log.add(LogType.MSG_PSE_CLEARSIGN_ONLY, indent);
|
||||
}
|
||||
|
||||
if (enableSignature) {
|
||||
updateProgress(R.string.progress_generating_signature, 95, 100);
|
||||
try {
|
||||
signatureGenerator.generate().encode(pOut);
|
||||
if (detachedBcpgOut != null) {
|
||||
signatureGenerator.generate().encode(detachedBcpgOut);
|
||||
} else {
|
||||
signatureGenerator.generate().encode(pOut);
|
||||
}
|
||||
} catch (NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded e) {
|
||||
// this secret key diverts to a OpenPGP card, throw exception with hash that will be signed
|
||||
log.add(LogType.MSG_SE_PENDING_NFC, indent);
|
||||
SignEncryptResult result =
|
||||
new SignEncryptResult(SignEncryptResult.RESULT_PENDING_NFC, log);
|
||||
log.add(LogType.MSG_PSE_PENDING_NFC, indent);
|
||||
PgpSignEncryptResult result =
|
||||
new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_PENDING_NFC, log);
|
||||
// Note that the checked key here is the master key, not the signing key
|
||||
// (although these are always the same on Yubikeys)
|
||||
result.setNfcData(mSignatureSubKeyId, e.hashToSign, e.hashAlgo, e.creationTimestamp, mSignaturePassphrase);
|
||||
Log.d(Constants.TAG, "e.hashToSign"+ Hex.toHexString(e.hashToSign));
|
||||
result.setNfcData(input.getSignatureSubKeyId(), e.hashToSign, e.hashAlgo, e.creationTimestamp, input.getSignaturePassphrase());
|
||||
Log.d(Constants.TAG, "e.hashToSign" + Hex.toHexString(e.hashToSign));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// closing outputs
|
||||
// NOTE: closing needs to be done in the correct order!
|
||||
// TODO: closing bcpgOut and pOut???
|
||||
if (enableEncryption) {
|
||||
if (enableCompression) {
|
||||
if (encryptionOut != null) {
|
||||
if (compressGen != null) {
|
||||
compressGen.close();
|
||||
}
|
||||
|
||||
encryptionOut.close();
|
||||
}
|
||||
if (mEnableAsciiArmorOutput) {
|
||||
// Note: Closing ArmoredOutputStream does not close the underlying stream
|
||||
if (armorOut != null) {
|
||||
armorOut.close();
|
||||
}
|
||||
|
||||
out.close();
|
||||
mOutStream.close();
|
||||
// Note: Closing ArmoredOutputStream does not close the underlying stream
|
||||
if (detachedArmorOut != null) {
|
||||
detachedArmorOut.close();
|
||||
}
|
||||
// Also closes detachedBcpgOut
|
||||
if (detachedByteOut != null) {
|
||||
detachedByteOut.close();
|
||||
}
|
||||
if (out != null) {
|
||||
out.close();
|
||||
}
|
||||
if (outputStream != null) {
|
||||
outputStream.close();
|
||||
}
|
||||
|
||||
} catch (SignatureException e) {
|
||||
log.add(LogType.MSG_SE_ERROR_SIG, indent);
|
||||
return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log);
|
||||
log.add(LogType.MSG_PSE_ERROR_SIG, indent);
|
||||
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||
} catch (PGPException e) {
|
||||
log.add(LogType.MSG_SE_ERROR_PGP, indent);
|
||||
return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log);
|
||||
log.add(LogType.MSG_PSE_ERROR_PGP, indent);
|
||||
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||
} catch (IOException e) {
|
||||
log.add(LogType.MSG_SE_ERROR_IO, indent);
|
||||
return new SignEncryptResult(SignEncryptResult.RESULT_ERROR, log);
|
||||
log.add(LogType.MSG_PSE_ERROR_IO, indent);
|
||||
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
updateProgress(R.string.progress_done, 100, 100);
|
||||
|
||||
log.add(LogType.MSG_SE_OK, indent);
|
||||
return new SignEncryptResult(SignEncryptResult.RESULT_OK, log);
|
||||
|
||||
log.add(LogType.MSG_PSE_OK, indent);
|
||||
PgpSignEncryptResult result = new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_OK, log);
|
||||
if (detachedByteOut != null) {
|
||||
try {
|
||||
detachedByteOut.flush();
|
||||
detachedByteOut.close();
|
||||
} catch (IOException e) {
|
||||
// silently catch
|
||||
}
|
||||
result.setDetachedSignature(detachedByteOut.toByteArray());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove whitespaces on line endings
|
||||
*/
|
||||
private static void processLine(final String pLine, final ArmoredOutputStream pArmoredOutput,
|
||||
final PGPSignatureGenerator pSignatureGenerator)
|
||||
throws IOException, SignatureException {
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/** This parcel stores the input of one or more PgpSignEncrypt operations.
|
||||
* All operations will use the same general paramters, differing only in
|
||||
* input and output. Each input/output set depends on the paramters:
|
||||
*
|
||||
* - Each input uri is individually encrypted/signed
|
||||
* - If a byte array is supplied, it is treated as an input before uris are processed
|
||||
* - The number of output uris must match the number of input uris, plus one more
|
||||
* if there is a byte array present.
|
||||
* - Once the output uris are empty, there must be exactly one input (uri xor bytes)
|
||||
* left, which will be returned in a byte array as part of the result parcel.
|
||||
*
|
||||
*/
|
||||
public class SignEncryptParcel extends PgpSignEncryptInput implements Parcelable {
|
||||
|
||||
public ArrayList<Uri> mInputUris = new ArrayList<>();
|
||||
public ArrayList<Uri> mOutputUris = new ArrayList<>();
|
||||
public byte[] mBytes;
|
||||
|
||||
public SignEncryptParcel() {
|
||||
super();
|
||||
}
|
||||
|
||||
public SignEncryptParcel(Parcel src) {
|
||||
|
||||
// we do all of those here, so the PgpSignEncryptInput class doesn't have to be parcelable
|
||||
mVersionHeader = src.readString();
|
||||
mEnableAsciiArmorOutput = src.readInt() == 1;
|
||||
mCompressionId = src.readInt();
|
||||
mEncryptionMasterKeyIds = src.createLongArray();
|
||||
mSymmetricPassphrase = src.readString();
|
||||
mSymmetricEncryptionAlgorithm = src.readInt();
|
||||
mSignatureMasterKeyId = src.readLong();
|
||||
mSignatureSubKeyId = src.readInt() == 1 ? src.readLong() : null;
|
||||
mSignatureHashAlgorithm = src.readInt();
|
||||
mSignaturePassphrase = src.readString();
|
||||
mAdditionalEncryptId = src.readLong();
|
||||
mNfcSignedHash = src.createByteArray();
|
||||
mNfcCreationTimestamp = src.readInt() == 1 ? new Date(src.readLong()) : null;
|
||||
mFailOnMissingEncryptionKeyIds = src.readInt() == 1;
|
||||
mCharset = src.readString();
|
||||
mCleartextSignature = src.readInt() == 1;
|
||||
mDetachedSignature = src.readInt() == 1;
|
||||
|
||||
mInputUris = src.createTypedArrayList(Uri.CREATOR);
|
||||
mOutputUris = src.createTypedArrayList(Uri.CREATOR);
|
||||
mBytes = src.createByteArray();
|
||||
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
return mBytes;
|
||||
}
|
||||
|
||||
public void setBytes(byte[] bytes) {
|
||||
mBytes = bytes;
|
||||
}
|
||||
|
||||
public List<Uri> getInputUris() {
|
||||
return Collections.unmodifiableList(mInputUris);
|
||||
}
|
||||
|
||||
public void addInputUris(Collection<Uri> inputUris) {
|
||||
mInputUris.addAll(inputUris);
|
||||
}
|
||||
|
||||
public List<Uri> getOutputUris() {
|
||||
return Collections.unmodifiableList(mOutputUris);
|
||||
}
|
||||
|
||||
public void addOutputUris(ArrayList<Uri> outputUris) {
|
||||
mOutputUris.addAll(outputUris);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(mVersionHeader);
|
||||
dest.writeInt(mEnableAsciiArmorOutput ? 1 : 0);
|
||||
dest.writeInt(mCompressionId);
|
||||
dest.writeLongArray(mEncryptionMasterKeyIds);
|
||||
dest.writeString(mSymmetricPassphrase);
|
||||
dest.writeInt(mSymmetricEncryptionAlgorithm);
|
||||
dest.writeLong(mSignatureMasterKeyId);
|
||||
if (mSignatureSubKeyId != null) {
|
||||
dest.writeInt(1);
|
||||
dest.writeLong(mSignatureSubKeyId);
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
dest.writeInt(mSignatureHashAlgorithm);
|
||||
dest.writeString(mSignaturePassphrase);
|
||||
dest.writeLong(mAdditionalEncryptId);
|
||||
dest.writeByteArray(mNfcSignedHash);
|
||||
if (mNfcCreationTimestamp != null) {
|
||||
dest.writeInt(1);
|
||||
dest.writeLong(mNfcCreationTimestamp.getTime());
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
dest.writeInt(mFailOnMissingEncryptionKeyIds ? 1 : 0);
|
||||
dest.writeString(mCharset);
|
||||
dest.writeInt(mCleartextSignature ? 1 : 0);
|
||||
dest.writeInt(mDetachedSignature ? 1 : 0);
|
||||
|
||||
dest.writeTypedList(mInputUris);
|
||||
dest.writeTypedList(mOutputUris);
|
||||
dest.writeByteArray(mBytes);
|
||||
}
|
||||
|
||||
public static final Creator<SignEncryptParcel> CREATOR = new Creator<SignEncryptParcel>() {
|
||||
public SignEncryptParcel createFromParcel(final Parcel source) {
|
||||
return new SignEncryptParcel(source);
|
||||
}
|
||||
|
||||
public SignEncryptParcel[] newArray(final int size) {
|
||||
return new SignEncryptParcel[size];
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -35,9 +35,9 @@ import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector;
|
||||
import org.spongycastle.openpgp.PGPUtil;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
@@ -445,7 +445,7 @@ public class UncachedKeyRing {
|
||||
}
|
||||
}
|
||||
|
||||
ArrayList<String> processedUserIds = new ArrayList<String>();
|
||||
ArrayList<String> processedUserIds = new ArrayList<>();
|
||||
for (byte[] rawUserId : new IterableIterator<byte[]>(masterKey.getRawUserIDs())) {
|
||||
String userId = Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(rawUserId);
|
||||
|
||||
@@ -470,7 +470,7 @@ public class UncachedKeyRing {
|
||||
@SuppressWarnings("unchecked")
|
||||
Iterator<PGPSignature> signaturesIt = masterKey.getSignaturesForID(rawUserId);
|
||||
if (signaturesIt != null) {
|
||||
for (PGPSignature zert : new IterableIterator<PGPSignature>(signaturesIt)) {
|
||||
for (PGPSignature zert : new IterableIterator<>(signaturesIt)) {
|
||||
WrappedSignature cert = new WrappedSignature(zert);
|
||||
long certId = cert.getKeyId();
|
||||
|
||||
@@ -635,7 +635,7 @@ public class UncachedKeyRing {
|
||||
@SuppressWarnings("unchecked")
|
||||
Iterator<PGPSignature> signaturesIt = masterKey.getSignaturesForUserAttribute(userAttribute);
|
||||
if (signaturesIt != null) {
|
||||
for (PGPSignature zert : new IterableIterator<PGPSignature>(signaturesIt)) {
|
||||
for (PGPSignature zert : new IterableIterator<>(signaturesIt)) {
|
||||
WrappedSignature cert = new WrappedSignature(zert);
|
||||
long certId = cert.getKeyId();
|
||||
|
||||
@@ -778,7 +778,7 @@ public class UncachedKeyRing {
|
||||
}
|
||||
|
||||
// Keep track of ids we encountered so far
|
||||
Set<Long> knownIds = new HashSet<Long>();
|
||||
Set<Long> knownIds = new HashSet<>();
|
||||
|
||||
// Process all keys
|
||||
for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(ring.getPublicKeys())) {
|
||||
@@ -1042,7 +1042,7 @@ public class UncachedKeyRing {
|
||||
}
|
||||
|
||||
// remember which certs we already added. this is cheaper than semantic deduplication
|
||||
Set<byte[]> certs = new TreeSet<byte[]>(new Comparator<byte[]>() {
|
||||
Set<byte[]> certs = new TreeSet<>(new Comparator<byte[]>() {
|
||||
public int compare(byte[] left, byte[] right) {
|
||||
// check for length equality
|
||||
if (left.length != right.length) {
|
||||
@@ -1124,7 +1124,7 @@ public class UncachedKeyRing {
|
||||
if (signaturesIt == null) {
|
||||
continue;
|
||||
}
|
||||
for (PGPSignature cert : new IterableIterator<PGPSignature>(signaturesIt)) {
|
||||
for (PGPSignature cert : new IterableIterator<>(signaturesIt)) {
|
||||
// Don't merge foreign stuff into secret keys
|
||||
if (cert.getKeyID() != masterKeyId && isSecret()) {
|
||||
continue;
|
||||
@@ -1149,7 +1149,7 @@ public class UncachedKeyRing {
|
||||
if (signaturesIt == null) {
|
||||
continue;
|
||||
}
|
||||
for (PGPSignature cert : new IterableIterator<PGPSignature>(signaturesIt)) {
|
||||
for (PGPSignature cert : new IterableIterator<>(signaturesIt)) {
|
||||
// Don't merge foreign stuff into secret keys
|
||||
if (cert.getKeyID() != masterKeyId && isSecret()) {
|
||||
continue;
|
||||
|
||||
+66
-33
@@ -20,7 +20,6 @@ package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import org.spongycastle.bcpg.ECPublicBCPGKey;
|
||||
import org.spongycastle.bcpg.SignatureSubpacketTags;
|
||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPSignature;
|
||||
import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
|
||||
@@ -51,7 +50,7 @@ public class UncachedPublicKey {
|
||||
}
|
||||
|
||||
/** The revocation signature is NOT checked here, so this may be false! */
|
||||
public boolean isRevoked() {
|
||||
public boolean isMaybeRevoked() {
|
||||
return mPublicKey.getSignaturesOfType(isMasterKey()
|
||||
? PGPSignature.KEY_REVOCATION
|
||||
: PGPSignature.SUBKEY_REVOCATION).hasNext();
|
||||
@@ -61,25 +60,8 @@ public class UncachedPublicKey {
|
||||
return mPublicKey.getCreationTime();
|
||||
}
|
||||
|
||||
public Date getExpiryTime() {
|
||||
long seconds = mPublicKey.getValidSeconds();
|
||||
if (seconds > Integer.MAX_VALUE) {
|
||||
Log.e(Constants.TAG, "error, expiry time too large");
|
||||
return null;
|
||||
}
|
||||
if (seconds == 0) {
|
||||
// no expiry
|
||||
return null;
|
||||
}
|
||||
Date creationDate = getCreationTime();
|
||||
Calendar calendar = GregorianCalendar.getInstance();
|
||||
calendar.setTime(creationDate);
|
||||
calendar.add(Calendar.SECOND, (int) seconds);
|
||||
|
||||
return calendar.getTime();
|
||||
}
|
||||
|
||||
public boolean isExpired() {
|
||||
/** The revocation signature is NOT checked here, so this may be false! */
|
||||
public boolean isMaybeExpired() {
|
||||
Date creationDate = mPublicKey.getCreationTime();
|
||||
Date expiryDate = mPublicKey.getValidSeconds() > 0
|
||||
? new Date(creationDate.getTime() + mPublicKey.getValidSeconds() * 1000) : null;
|
||||
@@ -136,7 +118,7 @@ public class UncachedPublicKey {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (PGPSignature sig : new IterableIterator<PGPSignature>(signaturesIt)) {
|
||||
for (PGPSignature sig : new IterableIterator<>(signaturesIt)) {
|
||||
try {
|
||||
|
||||
// if this is a revocation, this is not the user id
|
||||
@@ -200,7 +182,7 @@ public class UncachedPublicKey {
|
||||
}
|
||||
|
||||
public ArrayList<String> getUnorderedUserIds() {
|
||||
ArrayList<String> userIds = new ArrayList<String>();
|
||||
ArrayList<String> userIds = new ArrayList<>();
|
||||
for (byte[] rawUserId : new IterableIterator<byte[]>(mPublicKey.getRawUserIDs())) {
|
||||
// use our decoding method
|
||||
userIds.add(Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(rawUserId));
|
||||
@@ -209,7 +191,7 @@ public class UncachedPublicKey {
|
||||
}
|
||||
|
||||
public ArrayList<byte[]> getUnorderedRawUserIds() {
|
||||
ArrayList<byte[]> userIds = new ArrayList<byte[]>();
|
||||
ArrayList<byte[]> userIds = new ArrayList<>();
|
||||
for (byte[] userId : new IterableIterator<byte[]>(mPublicKey.getRawUserIDs())) {
|
||||
userIds.add(userId);
|
||||
}
|
||||
@@ -217,7 +199,7 @@ public class UncachedPublicKey {
|
||||
}
|
||||
|
||||
public ArrayList<WrappedUserAttribute> getUnorderedUserAttributes() {
|
||||
ArrayList<WrappedUserAttribute> userAttributes = new ArrayList<WrappedUserAttribute>();
|
||||
ArrayList<WrappedUserAttribute> userAttributes = new ArrayList<>();
|
||||
for (PGPUserAttributeSubpacketVector userAttribute :
|
||||
new IterableIterator<PGPUserAttributeSubpacketVector>(mPublicKey.getUserAttributes())) {
|
||||
userAttributes.add(new WrappedUserAttribute(userAttribute));
|
||||
@@ -305,28 +287,79 @@ public class UncachedPublicKey {
|
||||
*
|
||||
* Note that this method has package visiblity because it is used in test
|
||||
* cases. Certificates of UncachedPublicKey instances can NOT be assumed to
|
||||
* be verified, so the result of this method should not be used in other
|
||||
* places!
|
||||
* be verified or even by the correct key, so the result of this method
|
||||
* should never be used in other places!
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
Integer getKeyUsage() {
|
||||
if (mCacheUsage == null) {
|
||||
PGPSignature mostRecentSig = null;
|
||||
for (PGPSignature sig : new IterableIterator<PGPSignature>(mPublicKey.getSignatures())) {
|
||||
if (mPublicKey.isMasterKey() && sig.getKeyID() != mPublicKey.getKeyID()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets();
|
||||
switch (sig.getSignatureType()) {
|
||||
case PGPSignature.DEFAULT_CERTIFICATION:
|
||||
case PGPSignature.POSITIVE_CERTIFICATION:
|
||||
case PGPSignature.CASUAL_CERTIFICATION:
|
||||
case PGPSignature.NO_CERTIFICATION:
|
||||
case PGPSignature.SUBKEY_BINDING:
|
||||
break;
|
||||
// if this is not one of the above types, don't care
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we have no sig yet, take the first we can get
|
||||
if (mostRecentSig == null) {
|
||||
mostRecentSig = sig;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the new sig is less recent, skip it
|
||||
if (mostRecentSig.getCreationTime().after(sig.getCreationTime())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, note it down as the new "most recent" one
|
||||
mostRecentSig = sig;
|
||||
}
|
||||
|
||||
// Initialize to 0 as cached but empty value, if there is no sig (can't happen
|
||||
// for canonicalized keyring), or there is no KEY_FLAGS packet in the sig
|
||||
mCacheUsage = 0;
|
||||
if (mostRecentSig != null) {
|
||||
// If a mostRecentSig has been found, (cache and) return its flags
|
||||
PGPSignatureSubpacketVector hashed = mostRecentSig.getHashedSubPackets();
|
||||
if (hashed != null && hashed.getSubpacket(SignatureSubpacketTags.KEY_FLAGS) != null) {
|
||||
// init if at least one key flag subpacket has been found
|
||||
if (mCacheUsage == null) {
|
||||
mCacheUsage = 0;
|
||||
}
|
||||
mCacheUsage |= hashed.getKeyFlags();
|
||||
mCacheUsage = hashed.getKeyFlags();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return mCacheUsage;
|
||||
}
|
||||
|
||||
// this method relies on UNSAFE assumptions about the keyring, and should ONLY be used for
|
||||
// TEST CASES!!
|
||||
Date getUnsafeExpiryTimeForTesting () {
|
||||
long valid = mPublicKey.getValidSeconds();
|
||||
|
||||
if (valid > Integer.MAX_VALUE) {
|
||||
Log.e(Constants.TAG, "error, expiry time too large");
|
||||
return null;
|
||||
}
|
||||
if (valid == 0) {
|
||||
// no expiry
|
||||
return null;
|
||||
}
|
||||
Date creationDate = getCreationTime();
|
||||
Calendar calendar = GregorianCalendar.getInstance();
|
||||
calendar.setTime(creationDate);
|
||||
calendar.add(Calendar.SECOND, (int) valid);
|
||||
|
||||
return calendar.getTime();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+6
-3
@@ -36,7 +36,6 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.SignatureException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
@@ -79,8 +78,12 @@ public class WrappedSignature {
|
||||
return mSig.getCreationTime();
|
||||
}
|
||||
|
||||
public long getKeyExpirySeconds() {
|
||||
return mSig.getHashedSubPackets().getKeyExpirationTime();
|
||||
}
|
||||
|
||||
public ArrayList<WrappedSignature> getEmbeddedSignatures() {
|
||||
ArrayList<WrappedSignature> sigs = new ArrayList<WrappedSignature>();
|
||||
ArrayList<WrappedSignature> sigs = new ArrayList<>();
|
||||
if (!mSig.hasSubpackets()) {
|
||||
return sigs;
|
||||
}
|
||||
@@ -255,7 +258,7 @@ public class WrappedSignature {
|
||||
}
|
||||
|
||||
public HashMap<String,String> getNotation() {
|
||||
HashMap<String,String> result = new HashMap<String,String>();
|
||||
HashMap<String,String> result = new HashMap<>();
|
||||
|
||||
// If there is any notation data
|
||||
if (mSig.getHashedSubPackets() != null
|
||||
|
||||
+3
-1
@@ -1,5 +1,8 @@
|
||||
/*
|
||||
<<<<<<< HEAD
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
=======
|
||||
>>>>>>> development
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@@ -33,7 +36,6 @@ import java.io.IOException;
|
||||
import java.io.ObjectStreamException;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class WrappedUserAttribute implements Serializable {
|
||||
|
||||
|
||||
+22
@@ -85,6 +85,11 @@ public class KeychainContract {
|
||||
String PACKAGE_NAME = "package_name"; // foreign key to api_apps.package_name
|
||||
}
|
||||
|
||||
interface ApiAppsAllowedKeysColumns {
|
||||
String KEY_ID = "key_id"; // not a database id
|
||||
String PACKAGE_NAME = "package_name"; // foreign key to api_apps.package_name
|
||||
}
|
||||
|
||||
public static final String CONTENT_AUTHORITY = Constants.PACKAGE_NAME + ".provider";
|
||||
|
||||
private static final Uri BASE_CONTENT_URI_INTERNAL = Uri
|
||||
@@ -106,6 +111,7 @@ public class KeychainContract {
|
||||
|
||||
public static final String BASE_API_APPS = "api_apps";
|
||||
public static final String PATH_ACCOUNTS = "accounts";
|
||||
public static final String PATH_ALLOWED_KEYS = "allowed_keys";
|
||||
|
||||
public static class KeyRings implements BaseColumns, KeysColumns, UserPacketsColumns {
|
||||
public static final String MASTER_KEY_ID = KeysColumns.MASTER_KEY_ID;
|
||||
@@ -305,6 +311,22 @@ public class KeychainContract {
|
||||
}
|
||||
}
|
||||
|
||||
public static class ApiAllowedKeys implements ApiAppsAllowedKeysColumns, BaseColumns {
|
||||
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
|
||||
.appendPath(BASE_API_APPS).build();
|
||||
|
||||
/**
|
||||
* Use if multiple items get returned
|
||||
*/
|
||||
public static final String CONTENT_TYPE
|
||||
= "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.api_apps.allowed_keys";
|
||||
|
||||
public static Uri buildBaseUri(String packageName) {
|
||||
return CONTENT_URI.buildUpon().appendEncodedPath(packageName).appendPath(PATH_ALLOWED_KEYS)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
public static class Certs implements CertsColumns, BaseColumns {
|
||||
public static final String USER_ID = UserPacketsColumns.USER_ID;
|
||||
public static final String SIGNER_UID = "signer_user_id";
|
||||
|
||||
+25
-1
@@ -29,6 +29,7 @@ import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsAccountsColumns;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsAllowedKeysColumns;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsColumns;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.CertsColumns;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns;
|
||||
@@ -52,7 +53,7 @@ import java.io.IOException;
|
||||
*/
|
||||
public class KeychainDatabase extends SQLiteOpenHelper {
|
||||
private static final String DATABASE_NAME = "openkeychain.db";
|
||||
private static final int DATABASE_VERSION = 7;
|
||||
private static final int DATABASE_VERSION = 8;
|
||||
static Boolean apgHack = false;
|
||||
private Context mContext;
|
||||
|
||||
@@ -64,6 +65,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
||||
String CERTS = "certs";
|
||||
String API_APPS = "api_apps";
|
||||
String API_ACCOUNTS = "api_accounts";
|
||||
String API_ALLOWED_KEYS = "api_allowed_keys";
|
||||
}
|
||||
|
||||
private static final String CREATE_KEYRINGS_PUBLIC =
|
||||
@@ -166,6 +168,18 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
||||
+ Tables.API_APPS + "(" + ApiAppsColumns.PACKAGE_NAME + ") ON DELETE CASCADE"
|
||||
+ ")";
|
||||
|
||||
private static final String CREATE_API_APPS_ALLOWED_KEYS =
|
||||
"CREATE TABLE IF NOT EXISTS " + Tables.API_ALLOWED_KEYS + " ("
|
||||
+ BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
+ ApiAppsAllowedKeysColumns.KEY_ID + " INTEGER, "
|
||||
+ ApiAppsAllowedKeysColumns.PACKAGE_NAME + " TEXT NOT NULL, "
|
||||
|
||||
+ "UNIQUE(" + ApiAppsAllowedKeysColumns.KEY_ID + ", "
|
||||
+ ApiAppsAllowedKeysColumns.PACKAGE_NAME + "), "
|
||||
+ "FOREIGN KEY(" + ApiAppsAllowedKeysColumns.PACKAGE_NAME + ") REFERENCES "
|
||||
+ Tables.API_APPS + "(" + ApiAppsAllowedKeysColumns.PACKAGE_NAME + ") ON DELETE CASCADE"
|
||||
+ ")";
|
||||
|
||||
KeychainDatabase(Context context) {
|
||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||
mContext = context;
|
||||
@@ -195,6 +209,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
||||
db.execSQL(CREATE_CERTS);
|
||||
db.execSQL(CREATE_API_APPS);
|
||||
db.execSQL(CREATE_API_APPS_ACCOUNTS);
|
||||
db.execSQL(CREATE_API_APPS_ALLOWED_KEYS);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -243,6 +258,15 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
||||
case 6:
|
||||
db.execSQL("ALTER TABLE user_ids ADD COLUMN type INTEGER");
|
||||
db.execSQL("ALTER TABLE user_ids ADD COLUMN attribute_data BLOB");
|
||||
case 7:
|
||||
// consolidate
|
||||
case 8:
|
||||
// new table for allowed key ids in API
|
||||
try {
|
||||
db.execSQL(CREATE_API_APPS_ALLOWED_KEYS);
|
||||
} catch (Exception e) {
|
||||
// never mind, the column probably already existed
|
||||
}
|
||||
}
|
||||
|
||||
// always do consolidate after upgrade
|
||||
|
||||
+51
-11
@@ -32,6 +32,7 @@ import android.text.TextUtils;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAccounts;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAllowedKeys;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
|
||||
@@ -63,9 +64,10 @@ public class KeychainProvider extends ContentProvider {
|
||||
private static final int KEY_RING_CERTS_SPECIFIC = 206;
|
||||
|
||||
private static final int API_APPS = 301;
|
||||
private static final int API_APPS_BY_PACKAGE_NAME = 303;
|
||||
private static final int API_ACCOUNTS = 304;
|
||||
private static final int API_ACCOUNTS_BY_ACCOUNT_NAME = 306;
|
||||
private static final int API_APPS_BY_PACKAGE_NAME = 302;
|
||||
private static final int API_ACCOUNTS = 303;
|
||||
private static final int API_ACCOUNTS_BY_ACCOUNT_NAME = 304;
|
||||
private static final int API_ALLOWED_KEYS = 305;
|
||||
|
||||
private static final int KEY_RINGS_FIND_BY_EMAIL = 400;
|
||||
private static final int KEY_RINGS_FIND_BY_SUBKEY = 401;
|
||||
@@ -162,6 +164,8 @@ public class KeychainProvider extends ContentProvider {
|
||||
*
|
||||
* api_apps/_/accounts
|
||||
* api_apps/_/accounts/_ (account name)
|
||||
*
|
||||
* api_apps/_/allowed_keys
|
||||
* </pre>
|
||||
*/
|
||||
matcher.addURI(authority, KeychainContract.BASE_API_APPS, API_APPS);
|
||||
@@ -172,6 +176,9 @@ public class KeychainProvider extends ContentProvider {
|
||||
matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/*/"
|
||||
+ KeychainContract.PATH_ACCOUNTS + "/*", API_ACCOUNTS_BY_ACCOUNT_NAME);
|
||||
|
||||
matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/*/"
|
||||
+ KeychainContract.PATH_ALLOWED_KEYS, API_ALLOWED_KEYS);
|
||||
|
||||
return matcher;
|
||||
}
|
||||
|
||||
@@ -223,6 +230,9 @@ public class KeychainProvider extends ContentProvider {
|
||||
case API_ACCOUNTS_BY_ACCOUNT_NAME:
|
||||
return ApiAccounts.CONTENT_ITEM_TYPE;
|
||||
|
||||
case API_ALLOWED_KEYS:
|
||||
return ApiAllowedKeys.CONTENT_TYPE;
|
||||
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown uri: " + uri);
|
||||
}
|
||||
@@ -248,7 +258,7 @@ public class KeychainProvider extends ContentProvider {
|
||||
case KEY_RINGS_UNIFIED:
|
||||
case KEY_RINGS_FIND_BY_EMAIL:
|
||||
case KEY_RINGS_FIND_BY_SUBKEY: {
|
||||
HashMap<String, String> projectionMap = new HashMap<String, String>();
|
||||
HashMap<String, String> projectionMap = new HashMap<>();
|
||||
projectionMap.put(KeyRings._ID, Tables.KEYS + ".oid AS _id");
|
||||
projectionMap.put(KeyRings.MASTER_KEY_ID, Tables.KEYS + "." + Keys.MASTER_KEY_ID);
|
||||
projectionMap.put(KeyRings.KEY_ID, Tables.KEYS + "." + Keys.KEY_ID);
|
||||
@@ -432,7 +442,7 @@ public class KeychainProvider extends ContentProvider {
|
||||
}
|
||||
|
||||
case KEY_RING_KEYS: {
|
||||
HashMap<String, String> projectionMap = new HashMap<String, String>();
|
||||
HashMap<String, String> projectionMap = new HashMap<>();
|
||||
projectionMap.put(Keys._ID, Tables.KEYS + ".oid AS _id");
|
||||
projectionMap.put(Keys.MASTER_KEY_ID, Tables.KEYS + "." + Keys.MASTER_KEY_ID);
|
||||
projectionMap.put(Keys.RANK, Tables.KEYS + "." + Keys.RANK);
|
||||
@@ -460,7 +470,7 @@ public class KeychainProvider extends ContentProvider {
|
||||
|
||||
case KEY_RINGS_USER_IDS:
|
||||
case KEY_RING_USER_IDS: {
|
||||
HashMap<String, String> projectionMap = new HashMap<String, String>();
|
||||
HashMap<String, String> projectionMap = new HashMap<>();
|
||||
projectionMap.put(UserPackets._ID, Tables.USER_PACKETS + ".oid AS _id");
|
||||
projectionMap.put(UserPackets.MASTER_KEY_ID, Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID);
|
||||
projectionMap.put(UserPackets.TYPE, Tables.USER_PACKETS + "." + UserPackets.TYPE);
|
||||
@@ -507,7 +517,7 @@ public class KeychainProvider extends ContentProvider {
|
||||
|
||||
case KEY_RINGS_PUBLIC:
|
||||
case KEY_RING_PUBLIC: {
|
||||
HashMap<String, String> projectionMap = new HashMap<String, String>();
|
||||
HashMap<String, String> projectionMap = new HashMap<>();
|
||||
projectionMap.put(KeyRingData._ID, Tables.KEY_RINGS_PUBLIC + ".oid AS _id");
|
||||
projectionMap.put(KeyRingData.MASTER_KEY_ID, KeyRingData.MASTER_KEY_ID);
|
||||
projectionMap.put(KeyRingData.KEY_RING_DATA, KeyRingData.KEY_RING_DATA);
|
||||
@@ -525,7 +535,7 @@ public class KeychainProvider extends ContentProvider {
|
||||
|
||||
case KEY_RINGS_SECRET:
|
||||
case KEY_RING_SECRET: {
|
||||
HashMap<String, String> projectionMap = new HashMap<String, String>();
|
||||
HashMap<String, String> projectionMap = new HashMap<>();
|
||||
projectionMap.put(KeyRingData._ID, Tables.KEY_RINGS_SECRET + ".oid AS _id");
|
||||
projectionMap.put(KeyRingData.MASTER_KEY_ID, KeyRingData.MASTER_KEY_ID);
|
||||
projectionMap.put(KeyRingData.KEY_RING_DATA, KeyRingData.KEY_RING_DATA);
|
||||
@@ -543,7 +553,7 @@ public class KeychainProvider extends ContentProvider {
|
||||
|
||||
case KEY_RING_CERTS:
|
||||
case KEY_RING_CERTS_SPECIFIC: {
|
||||
HashMap<String, String> projectionMap = new HashMap<String, String>();
|
||||
HashMap<String, String> projectionMap = new HashMap<>();
|
||||
projectionMap.put(Certs._ID, Tables.CERTS + ".oid AS " + Certs._ID);
|
||||
projectionMap.put(Certs.MASTER_KEY_ID, Tables.CERTS + "." + Certs.MASTER_KEY_ID);
|
||||
projectionMap.put(Certs.RANK, Tables.CERTS + "." + Certs.RANK);
|
||||
@@ -613,6 +623,12 @@ public class KeychainProvider extends ContentProvider {
|
||||
qb.appendWhere(" AND " + Tables.API_ACCOUNTS + "." + ApiAccounts.ACCOUNT_NAME + " = ");
|
||||
qb.appendWhereEscapeString(uri.getLastPathSegment());
|
||||
|
||||
break;
|
||||
case API_ALLOWED_KEYS:
|
||||
qb.setTables(Tables.API_ALLOWED_KEYS);
|
||||
qb.appendWhere(Tables.API_ALLOWED_KEYS + "." + ApiAccounts.PACKAGE_NAME + " = ");
|
||||
qb.appendWhereEscapeString(uri.getPathSegments().get(1));
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown URI " + uri + " (" + match + ")");
|
||||
@@ -701,7 +717,7 @@ public class KeychainProvider extends ContentProvider {
|
||||
db.insertOrThrow(Tables.API_APPS, null, values);
|
||||
break;
|
||||
|
||||
case API_ACCOUNTS:
|
||||
case API_ACCOUNTS: {
|
||||
// set foreign key automatically based on given uri
|
||||
// e.g., api_apps/com.example.app/accounts/
|
||||
String packageName = uri.getPathSegments().get(1);
|
||||
@@ -709,12 +725,21 @@ public class KeychainProvider extends ContentProvider {
|
||||
|
||||
db.insertOrThrow(Tables.API_ACCOUNTS, null, values);
|
||||
break;
|
||||
}
|
||||
case API_ALLOWED_KEYS: {
|
||||
// set foreign key automatically based on given uri
|
||||
// e.g., api_apps/com.example.app/allowed_keys/
|
||||
String packageName = uri.getPathSegments().get(1);
|
||||
values.put(ApiAllowedKeys.PACKAGE_NAME, packageName);
|
||||
|
||||
db.insertOrThrow(Tables.API_ALLOWED_KEYS, null, values);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown uri: " + uri);
|
||||
}
|
||||
|
||||
if(keyId != null) {
|
||||
if (keyId != null) {
|
||||
uri = KeyRings.buildGenericKeyRingUri(keyId);
|
||||
rowUri = uri;
|
||||
}
|
||||
@@ -777,6 +802,10 @@ public class KeychainProvider extends ContentProvider {
|
||||
count = db.delete(Tables.API_ACCOUNTS, buildDefaultApiAccountsSelection(uri, additionalSelection),
|
||||
selectionArgs);
|
||||
break;
|
||||
case API_ALLOWED_KEYS:
|
||||
count = db.delete(Tables.API_ALLOWED_KEYS, buildDefaultApiAllowedKeysSelection(uri, additionalSelection),
|
||||
selectionArgs);
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown uri: " + uri);
|
||||
}
|
||||
@@ -869,4 +898,15 @@ public class KeychainProvider extends ContentProvider {
|
||||
+ andSelection;
|
||||
}
|
||||
|
||||
private String buildDefaultApiAllowedKeysSelection(Uri uri, String selection) {
|
||||
String packageName = DatabaseUtils.sqlEscapeString(uri.getPathSegments().get(1));
|
||||
|
||||
String andSelection = "";
|
||||
if (!TextUtils.isEmpty(selection)) {
|
||||
andSelection = " AND (" + selection + ")";
|
||||
}
|
||||
|
||||
return ApiAllowedKeys.PACKAGE_NAME + "=" + packageName + andSelection;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+87
-37
@@ -37,6 +37,12 @@ import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||
import org.sufficientlysecure.keychain.operations.ImportExportOperation;
|
||||
import org.sufficientlysecure.keychain.operations.results.ConsolidateResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
||||
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.pgp.CanonicalizedPublicKey;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
|
||||
@@ -44,26 +50,27 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
import org.sufficientlysecure.keychain.operations.ImportExportOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.Progressable;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedPublicKey;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedSignature;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAllowedKeys;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
|
||||
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.remote.AccountSettings;
|
||||
import org.sufficientlysecure.keychain.remote.AppSettings;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.operations.results.ConsolidateResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
|
||||
import org.sufficientlysecure.keychain.util.ParcelableFileCache;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.ParcelableFileCache;
|
||||
import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
import org.sufficientlysecure.keychain.util.ProgressFixedScaler;
|
||||
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||
import org.sufficientlysecure.keychain.util.Utf8Util;
|
||||
@@ -170,7 +177,7 @@ public class ProviderHelper {
|
||||
Cursor cursor = mContentResolver.query(uri, proj, selection, null, null);
|
||||
|
||||
try {
|
||||
HashMap<String, Object> result = new HashMap<String, Object>(proj.length);
|
||||
HashMap<String, Object> result = new HashMap<>(proj.length);
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
int pos = 0;
|
||||
for (String p : proj) {
|
||||
@@ -221,7 +228,7 @@ public class ProviderHelper {
|
||||
}, KeyRings.HAS_ANY_SECRET + " = 1", null, null);
|
||||
|
||||
try {
|
||||
LongSparseArray<CanonicalizedPublicKey> result = new LongSparseArray<CanonicalizedPublicKey>();
|
||||
LongSparseArray<CanonicalizedPublicKey> result = new LongSparseArray<>();
|
||||
|
||||
if (cursor != null && cursor.moveToFirst()) do {
|
||||
long masterKeyId = cursor.getLong(0);
|
||||
@@ -350,7 +357,7 @@ public class ProviderHelper {
|
||||
mIndent += 1;
|
||||
|
||||
// save all keys and userIds included in keyRing object in database
|
||||
operations = new ArrayList<ContentProviderOperation>();
|
||||
operations = new ArrayList<>();
|
||||
|
||||
log(LogType.MSG_IP_INSERT_KEYRING);
|
||||
{ // insert keyring
|
||||
@@ -440,7 +447,7 @@ public class ProviderHelper {
|
||||
|
||||
// classify and order user ids. primary are moved to the front, revoked to the back,
|
||||
// otherwise the order in the keyfile is preserved.
|
||||
List<UserPacketItem> uids = new ArrayList<UserPacketItem>();
|
||||
List<UserPacketItem> uids = new ArrayList<>();
|
||||
|
||||
if (trustedKeys.size() == 0) {
|
||||
log(LogType.MSG_IP_UID_CLASSIFYING_ZERO);
|
||||
@@ -460,7 +467,7 @@ public class ProviderHelper {
|
||||
log(LogType.MSG_IP_UID_PROCESSING, userId);
|
||||
mIndent += 1;
|
||||
// look through signatures for this specific key
|
||||
for (WrappedSignature cert : new IterableIterator<WrappedSignature>(
|
||||
for (WrappedSignature cert : new IterableIterator<>(
|
||||
masterKey.getSignaturesForRawId(rawUserId))) {
|
||||
long certId = cert.getKeyId();
|
||||
// self signature
|
||||
@@ -472,7 +479,7 @@ public class ProviderHelper {
|
||||
item.selfCert = cert;
|
||||
item.isPrimary = cert.isPrimaryUserId();
|
||||
} else {
|
||||
item.isRevoked = true;
|
||||
item.selfRevocation = cert;
|
||||
log(LogType.MSG_IP_UID_REVOKED);
|
||||
}
|
||||
continue;
|
||||
@@ -560,7 +567,7 @@ public class ProviderHelper {
|
||||
}
|
||||
mIndent += 1;
|
||||
// look through signatures for this specific key
|
||||
for (WrappedSignature cert : new IterableIterator<WrappedSignature>(
|
||||
for (WrappedSignature cert : new IterableIterator<>(
|
||||
masterKey.getSignaturesForUserAttribute(userAttribute))) {
|
||||
long certId = cert.getKeyId();
|
||||
// self signature
|
||||
@@ -568,10 +575,11 @@ public class ProviderHelper {
|
||||
|
||||
// NOTE self-certificates are already verified during canonicalization,
|
||||
// AND we know there is at most one cert plus at most one revocation
|
||||
// AND the revocation only exists if there is no newer certification
|
||||
if (!cert.isRevocation()) {
|
||||
item.selfCert = cert;
|
||||
} else {
|
||||
item.isRevoked = true;
|
||||
item.selfRevocation = cert;
|
||||
log(LogType.MSG_IP_UAT_REVOKED);
|
||||
}
|
||||
continue;
|
||||
@@ -642,16 +650,21 @@ public class ProviderHelper {
|
||||
for (int userIdRank = 0; userIdRank < uids.size(); userIdRank++) {
|
||||
UserPacketItem item = uids.get(userIdRank);
|
||||
operations.add(buildUserIdOperations(masterKeyId, item, userIdRank));
|
||||
if (item.selfCert != null) {
|
||||
// TODO get rid of "self verified" status? this cannot even happen anymore!
|
||||
operations.add(buildCertOperations(masterKeyId, userIdRank, item.selfCert,
|
||||
selfCertsAreTrusted ? Certs.VERIFIED_SECRET : Certs.VERIFIED_SELF));
|
||||
|
||||
if (item.selfCert == null) {
|
||||
throw new AssertionError("User ids MUST be self-certified at this point!!");
|
||||
}
|
||||
// don't bother with trusted certs if the uid is revoked, anyways
|
||||
if (item.isRevoked) {
|
||||
|
||||
if (item.selfRevocation != null) {
|
||||
operations.add(buildCertOperations(masterKeyId, userIdRank, item.selfRevocation,
|
||||
Certs.VERIFIED_SELF));
|
||||
// don't bother with trusted certs if the uid is revoked, anyways
|
||||
continue;
|
||||
}
|
||||
|
||||
operations.add(buildCertOperations(masterKeyId, userIdRank, item.selfCert,
|
||||
selfCertsAreTrusted ? Certs.VERIFIED_SECRET : Certs.VERIFIED_SELF));
|
||||
|
||||
// iterate over signatures
|
||||
for (int i = 0; i < item.trustedCerts.size() ; i++) {
|
||||
WrappedSignature sig = item.trustedCerts.valueAt(i);
|
||||
@@ -710,12 +723,17 @@ public class ProviderHelper {
|
||||
String userId;
|
||||
byte[] attributeData;
|
||||
boolean isPrimary = false;
|
||||
boolean isRevoked = false;
|
||||
WrappedSignature selfCert;
|
||||
LongSparseArray<WrappedSignature> trustedCerts = new LongSparseArray<WrappedSignature>();
|
||||
WrappedSignature selfRevocation;
|
||||
LongSparseArray<WrappedSignature> trustedCerts = new LongSparseArray<>();
|
||||
|
||||
@Override
|
||||
public int compareTo(UserPacketItem o) {
|
||||
// revoked keys always come last!
|
||||
//noinspection DoubleNegation
|
||||
if ( (selfRevocation != null) != (o.selfRevocation != null)) {
|
||||
return selfRevocation != null ? 1 : -1;
|
||||
}
|
||||
// if one is a user id, but the other isn't, the user id always comes first.
|
||||
// we compare for null values here, so != is the correct operator!
|
||||
// noinspection NumberEquality
|
||||
@@ -726,10 +744,6 @@ public class ProviderHelper {
|
||||
if (isPrimary != o.isPrimary) {
|
||||
return isPrimary ? -1 : 1;
|
||||
}
|
||||
// revoked keys always come last!
|
||||
if (isRevoked != o.isRevoked) {
|
||||
return isRevoked ? 1 : -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -1053,6 +1067,11 @@ public class ProviderHelper {
|
||||
log.add(LogType.MSG_CON, indent);
|
||||
indent += 1;
|
||||
|
||||
if (mConsolidateCritical) {
|
||||
log.add(LogType.MSG_CON_RECURSIVE, indent);
|
||||
return new ConsolidateResult(ConsolidateResult.RESULT_OK, log);
|
||||
}
|
||||
|
||||
progress.setProgress(R.string.progress_con_saving, 0, 100);
|
||||
|
||||
// The consolidate operation can never be cancelled!
|
||||
@@ -1075,7 +1094,7 @@ public class ProviderHelper {
|
||||
// No keys existing might be a legitimate option, we write an empty file in that case
|
||||
cursor.moveToFirst();
|
||||
ParcelableFileCache<ParcelableKeyRing> cache =
|
||||
new ParcelableFileCache<ParcelableKeyRing>(mContext, "consolidate_secret.pcl");
|
||||
new ParcelableFileCache<>(mContext, "consolidate_secret.pcl");
|
||||
cache.writeCache(cursor.getCount(), new Iterator<ParcelableKeyRing>() {
|
||||
ParcelableKeyRing ring;
|
||||
|
||||
@@ -1137,7 +1156,7 @@ public class ProviderHelper {
|
||||
// No keys existing might be a legitimate option, we write an empty file in that case
|
||||
cursor.moveToFirst();
|
||||
ParcelableFileCache<ParcelableKeyRing> cache =
|
||||
new ParcelableFileCache<ParcelableKeyRing>(mContext, "consolidate_public.pcl");
|
||||
new ParcelableFileCache<>(mContext, "consolidate_public.pcl");
|
||||
cache.writeCache(cursor.getCount(), new Iterator<ParcelableKeyRing>() {
|
||||
ParcelableKeyRing ring;
|
||||
|
||||
@@ -1219,13 +1238,11 @@ public class ProviderHelper {
|
||||
log.add(LogType.MSG_CON_DB_CLEAR, indent);
|
||||
mContentResolver.delete(KeyRings.buildUnifiedKeyRingsUri(), null, null);
|
||||
|
||||
ParcelableFileCache<ParcelableKeyRing> cacheSecret =
|
||||
new ParcelableFileCache<ParcelableKeyRing>(mContext, "consolidate_secret.pcl");
|
||||
ParcelableFileCache<ParcelableKeyRing> cachePublic =
|
||||
new ParcelableFileCache<ParcelableKeyRing>(mContext, "consolidate_public.pcl");
|
||||
ParcelableFileCache<ParcelableKeyRing> cacheSecret, cachePublic;
|
||||
|
||||
// Set flag that we have a cached consolidation here
|
||||
try {
|
||||
cacheSecret = new ParcelableFileCache<>(mContext, "consolidate_secret.pcl");
|
||||
IteratorWithSize<ParcelableKeyRing> itSecrets = cacheSecret.readCache(false);
|
||||
int numSecrets = itSecrets.getSize();
|
||||
|
||||
@@ -1253,6 +1270,7 @@ public class ProviderHelper {
|
||||
|
||||
try {
|
||||
|
||||
cachePublic = new ParcelableFileCache<>(mContext, "consolidate_public.pcl");
|
||||
IteratorWithSize<ParcelableKeyRing> itPublics = cachePublic.readCache();
|
||||
int numPublics = itPublics.getSize();
|
||||
|
||||
@@ -1308,7 +1326,6 @@ public class ProviderHelper {
|
||||
|
||||
progress.setProgress(100, 100);
|
||||
log.add(LogType.MSG_CON_SUCCESS, indent);
|
||||
indent -= 1;
|
||||
|
||||
return new ConsolidateResult(ConsolidateResult.RESULT_OK, log);
|
||||
|
||||
@@ -1349,7 +1366,7 @@ public class ProviderHelper {
|
||||
values.put(UserPackets.USER_ID, item.userId);
|
||||
values.put(UserPackets.ATTRIBUTE_DATA, item.attributeData);
|
||||
values.put(UserPackets.IS_PRIMARY, item.isPrimary);
|
||||
values.put(UserPackets.IS_REVOKED, item.isRevoked);
|
||||
values.put(UserPackets.IS_REVOKED, item.selfRevocation != null);
|
||||
values.put(UserPackets.RANK, rank);
|
||||
|
||||
Uri uri = UserPackets.buildUserIdsUri(masterKeyId);
|
||||
@@ -1380,7 +1397,7 @@ public class ProviderHelper {
|
||||
public ArrayList<String> getRegisteredApiApps() {
|
||||
Cursor cursor = mContentResolver.query(ApiApps.CONTENT_URI, null, null, null, null);
|
||||
|
||||
ArrayList<String> packageNames = new ArrayList<String>();
|
||||
ArrayList<String> packageNames = new ArrayList<>();
|
||||
try {
|
||||
if (cursor != null) {
|
||||
int packageNameCol = cursor.getColumnIndex(ApiApps.PACKAGE_NAME);
|
||||
@@ -1485,7 +1502,7 @@ public class ProviderHelper {
|
||||
}
|
||||
|
||||
public Set<Long> getAllKeyIdsForApp(Uri uri) {
|
||||
Set<Long> keyIds = new HashSet<Long>();
|
||||
Set<Long> keyIds = new HashSet<>();
|
||||
|
||||
Cursor cursor = mContentResolver.query(uri, null, null, null, null);
|
||||
try {
|
||||
@@ -1504,8 +1521,41 @@ public class ProviderHelper {
|
||||
return keyIds;
|
||||
}
|
||||
|
||||
public Set<Long> getAllowedKeyIdsForApp(Uri uri) {
|
||||
Set<Long> keyIds = new HashSet<>();
|
||||
|
||||
Cursor cursor = mContentResolver.query(uri, null, null, null, null);
|
||||
try {
|
||||
if (cursor != null) {
|
||||
int keyIdColumn = cursor.getColumnIndex(KeychainContract.ApiAllowedKeys.KEY_ID);
|
||||
while (cursor.moveToNext()) {
|
||||
keyIds.add(cursor.getLong(keyIdColumn));
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
return keyIds;
|
||||
}
|
||||
|
||||
public void saveAllowedKeyIdsForApp(Uri uri, Set<Long> allowedKeyIds)
|
||||
throws RemoteException, OperationApplicationException {
|
||||
// wipe whole table of allowed keys for this account
|
||||
mContentResolver.delete(uri, null, null);
|
||||
|
||||
// re-insert allowed key ids
|
||||
for (Long keyId : allowedKeyIds) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(ApiAllowedKeys.KEY_ID, keyId);
|
||||
mContentResolver.insert(uri, values);
|
||||
}
|
||||
}
|
||||
|
||||
public Set<String> getAllFingerprints(Uri uri) {
|
||||
Set<String> fingerprints = new HashSet<String>();
|
||||
Set<String> fingerprints = new HashSet<>();
|
||||
String[] projection = new String[]{KeyRings.FINGERPRINT};
|
||||
Cursor cursor = mContentResolver.query(uri, projection, null, null, null);
|
||||
try {
|
||||
|
||||
+307
-300
@@ -26,35 +26,34 @@ import android.os.ParcelFileDescriptor;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.openintents.openpgp.IOpenPgpService;
|
||||
import org.openintents.openpgp.OpenPgpMetadata;
|
||||
import org.openintents.openpgp.OpenPgpError;
|
||||
import org.openintents.openpgp.OpenPgpMetadata;
|
||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.ui.NfcActivity;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
|
||||
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
|
||||
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.PgpDecryptVerify;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptInput;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpSignEncryptOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAccounts;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.remote.ui.RemoteServiceActivity;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEntryParcel;
|
||||
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
|
||||
import org.sufficientlysecure.keychain.ui.ImportKeysActivity;
|
||||
import org.sufficientlysecure.keychain.ui.NfcActivity;
|
||||
import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity;
|
||||
import org.sufficientlysecure.keychain.ui.ViewKeyActivity;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
@@ -85,9 +84,9 @@ public class OpenPgpService extends RemoteService {
|
||||
boolean missingUserIdsCheck = false;
|
||||
boolean duplicateUserIdsCheck = false;
|
||||
|
||||
ArrayList<Long> keyIds = new ArrayList<Long>();
|
||||
ArrayList<String> missingUserIds = new ArrayList<String>();
|
||||
ArrayList<String> duplicateUserIds = new ArrayList<String>();
|
||||
ArrayList<Long> keyIds = new ArrayList<>();
|
||||
ArrayList<String> missingUserIds = new ArrayList<>();
|
||||
ArrayList<String> duplicateUserIds = new ArrayList<>();
|
||||
if (!noUserIdsCheck) {
|
||||
for (String email : encryptionUserIds) {
|
||||
// try to find the key for this specific email
|
||||
@@ -222,9 +221,12 @@ public class OpenPgpService extends RemoteService {
|
||||
}
|
||||
|
||||
private Intent signImpl(Intent data, ParcelFileDescriptor input,
|
||||
ParcelFileDescriptor output, AccountSettings accSettings) {
|
||||
ParcelFileDescriptor output, AccountSettings accSettings,
|
||||
boolean cleartextSign) {
|
||||
InputStream is = null;
|
||||
OutputStream os = null;
|
||||
try {
|
||||
boolean asciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
||||
boolean asciiArmor = cleartextSign || data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
||||
|
||||
byte[] nfcSignedHash = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_SIGNED_HASH);
|
||||
if (nfcSignedHash != null) {
|
||||
@@ -242,91 +244,55 @@ public class OpenPgpService extends RemoteService {
|
||||
}
|
||||
|
||||
// Get Input- and OutputStream from ParcelFileDescriptor
|
||||
InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input);
|
||||
OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(output);
|
||||
try {
|
||||
long inputLength = is.available();
|
||||
InputData inputData = new InputData(is, inputLength);
|
||||
|
||||
// Find the appropriate subkey to sign with
|
||||
long sigSubKeyId;
|
||||
try {
|
||||
CachedPublicKeyRing signingRing =
|
||||
new ProviderHelper(this).getCachedPublicKeyRing(accSettings.getKeyId());
|
||||
sigSubKeyId = signingRing.getSecretSignId();
|
||||
} catch (PgpKeyNotFoundException e) {
|
||||
// secret key that is set for this account is deleted?
|
||||
// show account config again!
|
||||
return getCreateAccountIntent(data, getAccountName(data));
|
||||
}
|
||||
|
||||
// get passphrase from cache, if key has "no" passphrase, this returns an empty String
|
||||
String passphrase;
|
||||
if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
|
||||
passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE);
|
||||
} else {
|
||||
try {
|
||||
passphrase = PassphraseCacheService.getCachedPassphrase(getContext(),
|
||||
accSettings.getKeyId(), sigSubKeyId);
|
||||
} catch (PassphraseCacheService.KeyNotFoundException e) {
|
||||
// should happen earlier, but return again here if it happens
|
||||
return getCreateAccountIntent(data, getAccountName(data));
|
||||
}
|
||||
}
|
||||
if (passphrase == null) {
|
||||
// get PendingIntent for passphrase input, add it to given params and return to client
|
||||
return getPassphraseIntent(data, sigSubKeyId);
|
||||
}
|
||||
|
||||
// sign-only
|
||||
PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder(
|
||||
this, new ProviderHelper(getContext()), null,
|
||||
inputData, os
|
||||
);
|
||||
builder.setEnableAsciiArmorOutput(asciiArmor)
|
||||
.setVersionHeader(PgpHelper.getVersionForHeader(this))
|
||||
.setSignatureHashAlgorithm(accSettings.getHashAlgorithm())
|
||||
.setSignatureMasterKeyId(accSettings.getKeyId())
|
||||
.setSignatureSubKeyId(sigSubKeyId)
|
||||
.setSignaturePassphrase(passphrase)
|
||||
.setNfcState(nfcSignedHash, nfcCreationDate);
|
||||
|
||||
// TODO: currently always assume cleartext input, no sign-only of binary currently!
|
||||
builder.setCleartextInput(true);
|
||||
|
||||
// execute PGP operation!
|
||||
SignEncryptResult pgpResult = builder.build().execute();
|
||||
|
||||
if (pgpResult.isPending()) {
|
||||
if ((pgpResult.getResult() & SignEncryptResult.RESULT_PENDING_PASSPHRASE) ==
|
||||
SignEncryptResult.RESULT_PENDING_PASSPHRASE) {
|
||||
return getPassphraseIntent(data, pgpResult.getKeyIdPassphraseNeeded());
|
||||
} else if ((pgpResult.getResult() & SignEncryptResult.RESULT_PENDING_NFC) ==
|
||||
SignEncryptResult.RESULT_PENDING_NFC) {
|
||||
// return PendingIntent to execute NFC activity
|
||||
// pass through the signature creation timestamp to be used again on second execution
|
||||
// of PgpSignEncrypt when we have the signed hash!
|
||||
data.putExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, pgpResult.getNfcTimestamp().getTime());
|
||||
return getNfcSignIntent(data, pgpResult.getNfcKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcHash(), pgpResult.getNfcAlgo());
|
||||
} else {
|
||||
throw new PgpGeneralException(
|
||||
"Encountered unhandled type of pending action not supported by API!");
|
||||
}
|
||||
} else if (pgpResult.success()) {
|
||||
// see end of method
|
||||
} else {
|
||||
LogEntryParcel errorMsg = pgpResult.getLog().getLast();
|
||||
throw new Exception(getString(errorMsg.mType.getMsgId()));
|
||||
}
|
||||
|
||||
} finally {
|
||||
is.close();
|
||||
os.close();
|
||||
is = new ParcelFileDescriptor.AutoCloseInputStream(input);
|
||||
if (cleartextSign) {
|
||||
// output stream only needed for cleartext signatures,
|
||||
// detached signatures are returned as extra
|
||||
os = new ParcelFileDescriptor.AutoCloseOutputStream(output);
|
||||
}
|
||||
long inputLength = is.available();
|
||||
InputData inputData = new InputData(is, inputLength);
|
||||
|
||||
Intent result = new Intent();
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
||||
return result;
|
||||
// sign-only
|
||||
PgpSignEncryptInput pseInput = new PgpSignEncryptInput()
|
||||
.setEnableAsciiArmorOutput(asciiArmor)
|
||||
.setCleartextSignature(cleartextSign)
|
||||
.setDetachedSignature(!cleartextSign)
|
||||
.setVersionHeader(PgpHelper.getVersionForHeader(this))
|
||||
.setSignatureHashAlgorithm(accSettings.getHashAlgorithm())
|
||||
.setSignatureMasterKeyId(accSettings.getKeyId())
|
||||
.setNfcState(nfcSignedHash, nfcCreationDate);
|
||||
|
||||
// execute PGP operation!
|
||||
PgpSignEncryptOperation pse = new PgpSignEncryptOperation(this, new ProviderHelper(getContext()), null);
|
||||
PgpSignEncryptResult pgpResult = pse.execute(pseInput, inputData, os);
|
||||
|
||||
if (pgpResult.isPending()) {
|
||||
if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE) ==
|
||||
PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE) {
|
||||
return getPassphraseIntent(data, pgpResult.getKeyIdPassphraseNeeded());
|
||||
} else if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_NFC) ==
|
||||
PgpSignEncryptResult.RESULT_PENDING_NFC) {
|
||||
// return PendingIntent to execute NFC activity
|
||||
// pass through the signature creation timestamp to be used again on second execution
|
||||
// of PgpSignEncrypt when we have the signed hash!
|
||||
data.putExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, pgpResult.getNfcTimestamp().getTime());
|
||||
return getNfcSignIntent(data, pgpResult.getNfcKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcHash(), pgpResult.getNfcAlgo());
|
||||
} else {
|
||||
throw new PgpGeneralException(
|
||||
"Encountered unhandled type of pending action not supported by API!");
|
||||
}
|
||||
} else if (pgpResult.success()) {
|
||||
Intent result = new Intent();
|
||||
if (!cleartextSign) {
|
||||
result.putExtra(OpenPgpApi.RESULT_DETACHED_SIGNATURE, pgpResult.getDetachedSignature());
|
||||
}
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
||||
return result;
|
||||
} else {
|
||||
LogEntryParcel errorMsg = pgpResult.getLog().getLast();
|
||||
throw new Exception(getString(errorMsg.mType.getMsgId()));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.d(Constants.TAG, "signImpl", e);
|
||||
Intent result = new Intent();
|
||||
@@ -334,12 +300,29 @@ 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, AccountSettings accSettings,
|
||||
boolean sign) {
|
||||
InputStream is = null;
|
||||
OutputStream os = null;
|
||||
try {
|
||||
boolean asciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
||||
String originalFilename = data.getStringExtra(OpenPgpApi.EXTRA_ORIGINAL_FILENAME);
|
||||
@@ -365,99 +348,65 @@ public class OpenPgpService extends RemoteService {
|
||||
|
||||
// build InputData and write into OutputStream
|
||||
// Get Input- and OutputStream from ParcelFileDescriptor
|
||||
InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input);
|
||||
OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(output);
|
||||
try {
|
||||
long inputLength = is.available();
|
||||
InputData inputData = new InputData(is, inputLength);
|
||||
is = new ParcelFileDescriptor.AutoCloseInputStream(input);
|
||||
os = new ParcelFileDescriptor.AutoCloseOutputStream(output);
|
||||
|
||||
PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder(
|
||||
this, new ProviderHelper(getContext()), null, inputData, os
|
||||
);
|
||||
builder.setEnableAsciiArmorOutput(asciiArmor)
|
||||
.setVersionHeader(PgpHelper.getVersionForHeader(this))
|
||||
.setCompressionId(accSettings.getCompression())
|
||||
.setSymmetricEncryptionAlgorithm(accSettings.getEncryptionAlgorithm())
|
||||
.setEncryptionMasterKeyIds(keyIds)
|
||||
.setFailOnMissingEncryptionKeyIds(true)
|
||||
.setOriginalFilename(originalFilename)
|
||||
.setAdditionalEncryptId(accSettings.getKeyId()); // add acc key for encryption
|
||||
long inputLength = is.available();
|
||||
InputData inputData = new InputData(is, inputLength, originalFilename);
|
||||
|
||||
if (sign) {
|
||||
PgpSignEncryptInput pseInput = new PgpSignEncryptInput();
|
||||
pseInput.setEnableAsciiArmorOutput(asciiArmor)
|
||||
.setVersionHeader(PgpHelper.getVersionForHeader(this))
|
||||
.setCompressionId(accSettings.getCompression())
|
||||
.setSymmetricEncryptionAlgorithm(accSettings.getEncryptionAlgorithm())
|
||||
.setEncryptionMasterKeyIds(keyIds)
|
||||
.setFailOnMissingEncryptionKeyIds(true)
|
||||
.setAdditionalEncryptId(accSettings.getKeyId()); // add acc key for encryption
|
||||
|
||||
// Find the appropriate subkey to sign with
|
||||
long sigSubKeyId;
|
||||
try {
|
||||
CachedPublicKeyRing signingRing =
|
||||
new ProviderHelper(this).getCachedPublicKeyRing(accSettings.getKeyId());
|
||||
sigSubKeyId = signingRing.getSecretSignId();
|
||||
} catch (PgpKeyNotFoundException e) {
|
||||
// secret key that is set for this account is deleted?
|
||||
// show account config again!
|
||||
return getCreateAccountIntent(data, getAccountName(data));
|
||||
}
|
||||
if (sign) {
|
||||
|
||||
String passphrase;
|
||||
if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
|
||||
passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE);
|
||||
} else {
|
||||
passphrase = PassphraseCacheService.getCachedPassphrase(getContext(),
|
||||
accSettings.getKeyId(), sigSubKeyId);
|
||||
}
|
||||
if (passphrase == null) {
|
||||
// get PendingIntent for passphrase input, add it to given params and return to client
|
||||
return getPassphraseIntent(data, sigSubKeyId);
|
||||
}
|
||||
|
||||
byte[] nfcSignedHash = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_SIGNED_HASH);
|
||||
// carefully: only set if timestamp exists
|
||||
Date nfcCreationDate = null;
|
||||
long nfcCreationTimestamp = data.getLongExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, -1);
|
||||
if (nfcCreationTimestamp != -1) {
|
||||
nfcCreationDate = new Date(nfcCreationTimestamp);
|
||||
}
|
||||
|
||||
// sign and encrypt
|
||||
builder.setSignatureHashAlgorithm(accSettings.getHashAlgorithm())
|
||||
.setSignatureMasterKeyId(accSettings.getKeyId())
|
||||
.setSignatureSubKeyId(sigSubKeyId)
|
||||
.setSignaturePassphrase(passphrase)
|
||||
.setNfcState(nfcSignedHash, nfcCreationDate);
|
||||
byte[] nfcSignedHash = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_SIGNED_HASH);
|
||||
// carefully: only set if timestamp exists
|
||||
Date nfcCreationDate = null;
|
||||
long nfcCreationTimestamp = data.getLongExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, -1);
|
||||
if (nfcCreationTimestamp != -1) {
|
||||
nfcCreationDate = new Date(nfcCreationTimestamp);
|
||||
}
|
||||
|
||||
// execute PGP operation!
|
||||
SignEncryptResult pgpResult = builder.build().execute();
|
||||
|
||||
if (pgpResult.isPending()) {
|
||||
if ((pgpResult.getResult() & SignEncryptResult.RESULT_PENDING_PASSPHRASE) ==
|
||||
SignEncryptResult.RESULT_PENDING_PASSPHRASE) {
|
||||
return getPassphraseIntent(data, pgpResult.getKeyIdPassphraseNeeded());
|
||||
} else if ((pgpResult.getResult() & SignEncryptResult.RESULT_PENDING_NFC) ==
|
||||
SignEncryptResult.RESULT_PENDING_NFC) {
|
||||
// return PendingIntent to execute NFC activity
|
||||
// pass through the signature creation timestamp to be used again on second execution
|
||||
// of PgpSignEncrypt when we have the signed hash!
|
||||
data.putExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, pgpResult.getNfcTimestamp().getTime());
|
||||
return getNfcSignIntent(data, pgpResult.getNfcKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcHash(), pgpResult.getNfcAlgo());
|
||||
} else {
|
||||
throw new PgpGeneralException(
|
||||
"Encountered unhandled type of pending action not supported by API!");
|
||||
}
|
||||
} else if (pgpResult.success()) {
|
||||
// see end of method
|
||||
} else {
|
||||
LogEntryParcel errorMsg = pgpResult.getLog().getLast();
|
||||
throw new Exception(getString(errorMsg.mType.getMsgId()));
|
||||
}
|
||||
|
||||
} finally {
|
||||
is.close();
|
||||
os.close();
|
||||
// sign and encrypt
|
||||
pseInput.setSignatureHashAlgorithm(accSettings.getHashAlgorithm())
|
||||
.setSignatureMasterKeyId(accSettings.getKeyId())
|
||||
.setNfcState(nfcSignedHash, nfcCreationDate);
|
||||
}
|
||||
|
||||
Intent result = new Intent();
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
||||
return result;
|
||||
PgpSignEncryptOperation op = new PgpSignEncryptOperation(this, new ProviderHelper(getContext()), null);
|
||||
|
||||
// execute PGP operation!
|
||||
PgpSignEncryptResult pgpResult = op.execute(pseInput, inputData, os);
|
||||
|
||||
if (pgpResult.isPending()) {
|
||||
if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE) ==
|
||||
PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE) {
|
||||
return getPassphraseIntent(data, pgpResult.getKeyIdPassphraseNeeded());
|
||||
} else if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_NFC) ==
|
||||
PgpSignEncryptResult.RESULT_PENDING_NFC) {
|
||||
// return PendingIntent to execute NFC activity
|
||||
// pass through the signature creation timestamp to be used again on second execution
|
||||
// of PgpSignEncrypt when we have the signed hash!
|
||||
data.putExtra(OpenPgpApi.EXTRA_NFC_SIG_CREATION_TIMESTAMP, pgpResult.getNfcTimestamp().getTime());
|
||||
return getNfcSignIntent(data, pgpResult.getNfcKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcHash(), pgpResult.getNfcAlgo());
|
||||
} else {
|
||||
throw new PgpGeneralException(
|
||||
"Encountered unhandled type of pending action not supported by API!");
|
||||
}
|
||||
} else if (pgpResult.success()) {
|
||||
Intent result = new Intent();
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
||||
return result;
|
||||
} else {
|
||||
LogEntryParcel errorMsg = pgpResult.getLog().getLast();
|
||||
throw new Exception(getString(errorMsg.mType.getMsgId()));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.d(Constants.TAG, "encryptAndSignImpl", e);
|
||||
Intent result = new Intent();
|
||||
@@ -465,111 +414,129 @@ 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 input,
|
||||
ParcelFileDescriptor output, Set<Long> allowedKeyIds,
|
||||
boolean decryptMetadataOnly) {
|
||||
InputStream is = null;
|
||||
OutputStream os = null;
|
||||
try {
|
||||
// Get Input- and OutputStream from ParcelFileDescriptor
|
||||
InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input);
|
||||
is = new ParcelFileDescriptor.AutoCloseInputStream(input);
|
||||
|
||||
OutputStream os;
|
||||
if (decryptMetadataOnly) {
|
||||
// output is optional, e.g., for verifying detached signatures
|
||||
if (decryptMetadataOnly || output == null) {
|
||||
os = null;
|
||||
} else {
|
||||
os = new ParcelFileDescriptor.AutoCloseOutputStream(output);
|
||||
}
|
||||
|
||||
Intent result = new Intent();
|
||||
try {
|
||||
String passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE);
|
||||
long inputLength = is.available();
|
||||
InputData inputData = new InputData(is, inputLength);
|
||||
String passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE);
|
||||
long inputLength = is.available();
|
||||
InputData inputData = new InputData(is, inputLength);
|
||||
|
||||
PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(
|
||||
this, new ProviderHelper(getContext()), null, inputData, os
|
||||
);
|
||||
PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(
|
||||
this, new ProviderHelper(getContext()), null, inputData, os
|
||||
);
|
||||
|
||||
byte[] nfcDecryptedSessionKey = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_DECRYPTED_SESSION_KEY);
|
||||
byte[] nfcDecryptedSessionKey = data.getByteArrayExtra(OpenPgpApi.EXTRA_NFC_DECRYPTED_SESSION_KEY);
|
||||
|
||||
// allow only private keys associated with accounts of this app
|
||||
// no support for symmetric encryption
|
||||
builder.setPassphrase(passphrase)
|
||||
.setAllowSymmetricDecryption(false)
|
||||
.setAllowedKeyIds(allowedKeyIds)
|
||||
.setDecryptMetadataOnly(decryptMetadataOnly)
|
||||
.setNfcState(nfcDecryptedSessionKey);
|
||||
byte[] detachedSignature = data.getByteArrayExtra(OpenPgpApi.EXTRA_DETACHED_SIGNATURE);
|
||||
|
||||
// TODO: currently does not support binary signed-only content
|
||||
DecryptVerifyResult pgpResult = builder.build().execute();
|
||||
// allow only private keys associated with accounts of this app
|
||||
// no support for symmetric encryption
|
||||
builder.setPassphrase(passphrase)
|
||||
.setAllowSymmetricDecryption(false)
|
||||
.setAllowedKeyIds(allowedKeyIds)
|
||||
.setDecryptMetadataOnly(decryptMetadataOnly)
|
||||
.setNfcState(nfcDecryptedSessionKey)
|
||||
.setDetachedSignature(detachedSignature);
|
||||
|
||||
if (pgpResult.isPending()) {
|
||||
if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) ==
|
||||
DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) {
|
||||
return getPassphraseIntent(data, pgpResult.getKeyIdPassphraseNeeded());
|
||||
} else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) ==
|
||||
DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) {
|
||||
throw new PgpGeneralException(
|
||||
"Decryption of symmetric content not supported by API!");
|
||||
} else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_NFC) ==
|
||||
DecryptVerifyResult.RESULT_PENDING_NFC) {
|
||||
return getNfcDecryptIntent(
|
||||
data, pgpResult.getNfcSubKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcEncryptedSessionKey());
|
||||
} else {
|
||||
throw new PgpGeneralException(
|
||||
"Encountered unhandled type of pending action not supported by API!");
|
||||
}
|
||||
} else if (pgpResult.success()) {
|
||||
DecryptVerifyResult pgpResult = builder.build().execute();
|
||||
|
||||
OpenPgpSignatureResult signatureResult = pgpResult.getSignatureResult();
|
||||
if (signatureResult != null) {
|
||||
result.putExtra(OpenPgpApi.RESULT_SIGNATURE, signatureResult);
|
||||
|
||||
if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) < 5) {
|
||||
// SIGNATURE_KEY_REVOKED and SIGNATURE_KEY_EXPIRED have been added in version 5
|
||||
if (signatureResult.getStatus() == OpenPgpSignatureResult.SIGNATURE_KEY_REVOKED
|
||||
|| signatureResult.getStatus() == OpenPgpSignatureResult.SIGNATURE_KEY_EXPIRED) {
|
||||
signatureResult.setStatus(OpenPgpSignatureResult.SIGNATURE_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
if (signatureResult.getStatus() == OpenPgpSignatureResult.SIGNATURE_KEY_MISSING) {
|
||||
// If signature is unknown we return an _additional_ PendingIntent
|
||||
// to retrieve the missing key
|
||||
Intent intent = new Intent(getBaseContext(), ImportKeysActivity.class);
|
||||
intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE);
|
||||
intent.putExtra(ImportKeysActivity.EXTRA_KEY_ID, signatureResult.getKeyId());
|
||||
intent.putExtra(ImportKeysActivity.EXTRA_PENDING_INTENT_DATA, data);
|
||||
|
||||
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
|
||||
intent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
|
||||
result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) >= 4) {
|
||||
OpenPgpMetadata metadata = pgpResult.getDecryptMetadata();
|
||||
if (metadata != null) {
|
||||
result.putExtra(OpenPgpApi.RESULT_METADATA, metadata);
|
||||
}
|
||||
}
|
||||
if (pgpResult.isPending()) {
|
||||
if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) ==
|
||||
DecryptVerifyResult.RESULT_PENDING_ASYM_PASSPHRASE) {
|
||||
return getPassphraseIntent(data, pgpResult.getKeyIdPassphraseNeeded());
|
||||
} else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) ==
|
||||
DecryptVerifyResult.RESULT_PENDING_SYM_PASSPHRASE) {
|
||||
throw new PgpGeneralException(
|
||||
"Decryption of symmetric content not supported by API!");
|
||||
} else if ((pgpResult.getResult() & DecryptVerifyResult.RESULT_PENDING_NFC) ==
|
||||
DecryptVerifyResult.RESULT_PENDING_NFC) {
|
||||
return getNfcDecryptIntent(
|
||||
data, pgpResult.getNfcSubKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcEncryptedSessionKey());
|
||||
} else {
|
||||
LogEntryParcel errorMsg = pgpResult.getLog().getLast();
|
||||
throw new Exception(getString(errorMsg.mType.getMsgId()));
|
||||
throw new PgpGeneralException(
|
||||
"Encountered unhandled type of pending action not supported by API!");
|
||||
}
|
||||
} finally {
|
||||
is.close();
|
||||
if (os != null) {
|
||||
os.close();
|
||||
} else if (pgpResult.success()) {
|
||||
Intent result = new Intent();
|
||||
|
||||
OpenPgpSignatureResult signatureResult = pgpResult.getSignatureResult();
|
||||
if (signatureResult != null) {
|
||||
result.putExtra(OpenPgpApi.RESULT_SIGNATURE, signatureResult);
|
||||
|
||||
if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) < 5) {
|
||||
// SIGNATURE_KEY_REVOKED and SIGNATURE_KEY_EXPIRED have been added in version 5
|
||||
if (signatureResult.getStatus() == OpenPgpSignatureResult.SIGNATURE_KEY_REVOKED
|
||||
|| signatureResult.getStatus() == OpenPgpSignatureResult.SIGNATURE_KEY_EXPIRED) {
|
||||
signatureResult.setStatus(OpenPgpSignatureResult.SIGNATURE_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
if (signatureResult.getStatus() == OpenPgpSignatureResult.SIGNATURE_KEY_MISSING) {
|
||||
// If signature is unknown we return an _additional_ PendingIntent
|
||||
// to retrieve the missing key
|
||||
Intent intent = new Intent(getBaseContext(), ImportKeysActivity.class);
|
||||
intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE);
|
||||
intent.putExtra(ImportKeysActivity.EXTRA_KEY_ID, signatureResult.getKeyId());
|
||||
intent.putExtra(ImportKeysActivity.EXTRA_PENDING_INTENT_DATA, data);
|
||||
|
||||
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
|
||||
intent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
|
||||
result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) >= 4) {
|
||||
OpenPgpMetadata metadata = pgpResult.getDecryptMetadata();
|
||||
if (metadata != null) {
|
||||
result.putExtra(OpenPgpApi.RESULT_METADATA, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
String charset = pgpResult.getCharset();
|
||||
if (charset != null) {
|
||||
result.putExtra(OpenPgpApi.RESULT_CHARSET, charset);
|
||||
}
|
||||
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
||||
return result;
|
||||
} else {
|
||||
LogEntryParcel errorMsg = pgpResult.getLog().getLast();
|
||||
throw new Exception(getString(errorMsg.mType.getMsgId()));
|
||||
}
|
||||
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
Log.d(Constants.TAG, "decryptAndVerifyImpl", e);
|
||||
Intent result = new Intent();
|
||||
@@ -577,6 +544,21 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -668,15 +650,16 @@ public class OpenPgpService extends RemoteService {
|
||||
|
||||
// version code is required and needs to correspond to version code of service!
|
||||
// History of versions in org.openintents.openpgp.util.OpenPgpApi
|
||||
// we support 3, 4, 5
|
||||
// we support 3, 4, 5, 6
|
||||
if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != 3
|
||||
&& data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != 4
|
||||
&& data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != 5) {
|
||||
&& data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != 5
|
||||
&& data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != 6) {
|
||||
Intent result = new Intent();
|
||||
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: 3, 4");
|
||||
+ "supported API versions: 3, 4, 5, 6");
|
||||
result.putExtra(OpenPgpApi.RESULT_ERROR, error);
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
|
||||
return result;
|
||||
@@ -706,42 +689,66 @@ public class OpenPgpService extends RemoteService {
|
||||
|
||||
@Override
|
||||
public Intent execute(Intent data, ParcelFileDescriptor input, ParcelFileDescriptor output) {
|
||||
Intent errorResult = checkRequirements(data);
|
||||
if (errorResult != null) {
|
||||
return errorResult;
|
||||
}
|
||||
try {
|
||||
Intent errorResult = checkRequirements(data);
|
||||
if (errorResult != null) {
|
||||
return errorResult;
|
||||
}
|
||||
|
||||
String accName = getAccountName(data);
|
||||
final AccountSettings accSettings = getAccSettings(accName);
|
||||
if (accSettings == null) {
|
||||
return getCreateAccountIntent(data, accName);
|
||||
}
|
||||
String accName = getAccountName(data);
|
||||
final AccountSettings accSettings = getAccSettings(accName);
|
||||
if (accSettings == null) {
|
||||
return getCreateAccountIntent(data, accName);
|
||||
}
|
||||
|
||||
String action = data.getAction();
|
||||
if (OpenPgpApi.ACTION_SIGN.equals(action)) {
|
||||
return signImpl(data, input, output, accSettings);
|
||||
} else if (OpenPgpApi.ACTION_ENCRYPT.equals(action)) {
|
||||
return encryptAndSignImpl(data, input, output, accSettings, false);
|
||||
} else if (OpenPgpApi.ACTION_SIGN_AND_ENCRYPT.equals(action)) {
|
||||
return encryptAndSignImpl(data, input, output, accSettings, true);
|
||||
} else if (OpenPgpApi.ACTION_DECRYPT_VERIFY.equals(action)) {
|
||||
String currentPkg = getCurrentCallingPackage();
|
||||
Set<Long> allowedKeyIds =
|
||||
mProviderHelper.getAllKeyIdsForApp(
|
||||
ApiAccounts.buildBaseUri(currentPkg));
|
||||
return decryptAndVerifyImpl(data, input, output, allowedKeyIds, false);
|
||||
} else if (OpenPgpApi.ACTION_DECRYPT_METADATA.equals(action)) {
|
||||
String currentPkg = getCurrentCallingPackage();
|
||||
Set<Long> allowedKeyIds =
|
||||
mProviderHelper.getAllKeyIdsForApp(
|
||||
ApiAccounts.buildBaseUri(currentPkg));
|
||||
return decryptAndVerifyImpl(data, input, output, allowedKeyIds, true);
|
||||
} else if (OpenPgpApi.ACTION_GET_KEY.equals(action)) {
|
||||
return getKeyImpl(data);
|
||||
} else if (OpenPgpApi.ACTION_GET_KEY_IDS.equals(action)) {
|
||||
return getKeyIdsImpl(data);
|
||||
} else {
|
||||
return null;
|
||||
String action = data.getAction();
|
||||
if (OpenPgpApi.ACTION_CLEARTEXT_SIGN.equals(action)) {
|
||||
return signImpl(data, input, output, accSettings, true);
|
||||
} else if (OpenPgpApi.ACTION_SIGN.equals(action)) {
|
||||
// DEPRECATED: same as ACTION_CLEARTEXT_SIGN
|
||||
Log.w(Constants.TAG, "You are using a deprecated API call, please use ACTION_CLEARTEXT_SIGN instead of ACTION_SIGN!");
|
||||
return signImpl(data, input, output, accSettings, true);
|
||||
} else if (OpenPgpApi.ACTION_DETACHED_SIGN.equals(action)) {
|
||||
return signImpl(data, input, output, accSettings, false);
|
||||
} else if (OpenPgpApi.ACTION_ENCRYPT.equals(action)) {
|
||||
return encryptAndSignImpl(data, input, output, accSettings, false);
|
||||
} else if (OpenPgpApi.ACTION_SIGN_AND_ENCRYPT.equals(action)) {
|
||||
return encryptAndSignImpl(data, input, output, accSettings, true);
|
||||
} else if (OpenPgpApi.ACTION_DECRYPT_VERIFY.equals(action)) {
|
||||
String currentPkg = getCurrentCallingPackage();
|
||||
Set<Long> allowedKeyIds =
|
||||
mProviderHelper.getAllKeyIdsForApp(
|
||||
ApiAccounts.buildBaseUri(currentPkg));
|
||||
return decryptAndVerifyImpl(data, input, output, allowedKeyIds, false);
|
||||
} else if (OpenPgpApi.ACTION_DECRYPT_METADATA.equals(action)) {
|
||||
String currentPkg = getCurrentCallingPackage();
|
||||
Set<Long> allowedKeyIds =
|
||||
mProviderHelper.getAllKeyIdsForApp(
|
||||
ApiAccounts.buildBaseUri(currentPkg));
|
||||
return decryptAndVerifyImpl(data, input, output, allowedKeyIds, true);
|
||||
} else if (OpenPgpApi.ACTION_GET_KEY.equals(action)) {
|
||||
return getKeyImpl(data);
|
||||
} else if (OpenPgpApi.ACTION_GET_KEY_IDS.equals(action)) {
|
||||
return getKeyIdsImpl(data);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} finally {
|
||||
// always close input and output file descriptors even in error cases
|
||||
if (input != null) {
|
||||
try {
|
||||
input.close();
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "IOException when closing input ParcelFileDescriptor", e);
|
||||
}
|
||||
}
|
||||
if (output != null) {
|
||||
try {
|
||||
output.close();
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "IOException when closing output ParcelFileDescriptor", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-4
@@ -27,7 +27,6 @@ import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.pm.Signature;
|
||||
import android.net.Uri;
|
||||
import android.os.Binder;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.openintents.openpgp.OpenPgpError;
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
@@ -216,9 +215,7 @@ public abstract class RemoteService extends Service {
|
||||
String[] callingPackages = getPackageManager().getPackagesForUid(uid);
|
||||
|
||||
// is calling package allowed to use this service?
|
||||
for (int i = 0; i < callingPackages.length; i++) {
|
||||
String currentPkg = callingPackages[i];
|
||||
|
||||
for (String currentPkg : callingPackages) {
|
||||
if (isPackageAllowed(currentPkg)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
+17
-18
@@ -20,22 +20,21 @@ package org.sufficientlysecure.keychain.remote.ui;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.util.ActionBarHelper;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.remote.AccountSettings;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||
import org.sufficientlysecure.keychain.operations.results.SingletonResult;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.remote.AccountSettings;
|
||||
import org.sufficientlysecure.keychain.ui.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
public class AccountSettingsActivity extends ActionBarActivity {
|
||||
public class AccountSettingsActivity extends BaseActivity {
|
||||
private Uri mAccountUri;
|
||||
|
||||
private AccountSettingsFragment mAccountSettingsFragment;
|
||||
@@ -45,17 +44,20 @@ public class AccountSettingsActivity extends ActionBarActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Inflate a "Done" custom action bar
|
||||
ActionBarHelper.setOneButtonView(getSupportActionBar(),
|
||||
R.string.api_settings_save, R.drawable.ic_action_done,
|
||||
setFullScreenDialogDoneClose(R.string.api_settings_save,
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// "Done"
|
||||
save();
|
||||
}
|
||||
},
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
setContentView(R.layout.api_account_settings_activity);
|
||||
setTitle(null);
|
||||
|
||||
mAccountSettingsFragment = (AccountSettingsFragment) getSupportFragmentManager().findFragmentById(
|
||||
R.id.api_account_settings_fragment);
|
||||
@@ -72,6 +74,11 @@ public class AccountSettingsActivity extends ActionBarActivity {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initLayout() {
|
||||
setContentView(R.layout.api_account_settings_activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
@@ -85,9 +92,6 @@ public class AccountSettingsActivity extends ActionBarActivity {
|
||||
case R.id.menu_account_settings_delete:
|
||||
deleteAccount();
|
||||
return true;
|
||||
case R.id.menu_account_settings_cancel:
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
@@ -125,9 +129,4 @@ public class AccountSettingsActivity extends ActionBarActivity {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
save();
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -31,10 +31,10 @@ import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.remote.AccountSettings;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.remote.AccountSettings;
|
||||
import org.sufficientlysecure.keychain.ui.CreateKeyActivity;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.KeyValueSpinnerAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.widget.KeySpinner;
|
||||
|
||||
+116
-24
@@ -17,29 +17,54 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.remote.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.getbase.floatingactionbutton.FloatingActionButton;
|
||||
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.remote.AppSettings;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.ui.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.AddSubkeyDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.AdvancedAppSettingsDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
public class AppSettingsActivity extends ActionBarActivity {
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
public class AppSettingsActivity extends BaseActivity {
|
||||
private Uri mAppUri;
|
||||
|
||||
private AppSettingsFragment mSettingsFragment;
|
||||
private AppSettingsAllowedKeysListFragment mAllowedKeysFragment;
|
||||
|
||||
private TextView mAppNameView;
|
||||
private ImageView mAppIconView;
|
||||
private TextView mPackageName;
|
||||
private TextView mPackageSignature;
|
||||
|
||||
private FloatingActionButton mStartFab;
|
||||
|
||||
// deprecated API
|
||||
private AccountsListFragment mAccountsListFragment;
|
||||
private TextView mAccountsLabel;
|
||||
|
||||
|
||||
// model
|
||||
AppSettings mAppSettings;
|
||||
@@ -48,16 +73,27 @@ public class AppSettingsActivity extends ActionBarActivity {
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// let the actionbar look like Android's contact app
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
actionBar.setIcon(android.R.color.transparent);
|
||||
actionBar.setHomeButtonEnabled(true);
|
||||
mAccountsLabel = (TextView) findViewById(R.id.api_accounts_label);
|
||||
mAppNameView = (TextView) findViewById(R.id.api_app_settings_app_name);
|
||||
mAppIconView = (ImageView) findViewById(R.id.api_app_settings_app_icon);
|
||||
mPackageName = (TextView) findViewById(R.id.api_app_settings_package_name);
|
||||
mPackageSignature = (TextView) findViewById(R.id.api_app_settings_package_signature);
|
||||
mStartFab = (FloatingActionButton) findViewById(R.id.fab);
|
||||
|
||||
setContentView(R.layout.api_app_settings_activity);
|
||||
mStartFab.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
startApp();
|
||||
}
|
||||
});
|
||||
|
||||
mSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById(
|
||||
R.id.api_app_settings_fragment);
|
||||
setFullScreenDialogClose(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
cancel();
|
||||
}
|
||||
});
|
||||
setTitle(null);
|
||||
|
||||
Intent intent = getIntent();
|
||||
mAppUri = intent.getData();
|
||||
@@ -71,6 +107,22 @@ public class AppSettingsActivity extends ActionBarActivity {
|
||||
}
|
||||
}
|
||||
|
||||
private void save() {
|
||||
mAllowedKeysFragment.saveAllowedKeys();
|
||||
setResult(Activity.RESULT_OK);
|
||||
finish();
|
||||
}
|
||||
|
||||
private void cancel() {
|
||||
setResult(Activity.RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initLayout() {
|
||||
setContentView(R.layout.api_app_settings_activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
@@ -81,16 +133,40 @@ public class AppSettingsActivity extends ActionBarActivity {
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_api_settings_revoke:
|
||||
case R.id.menu_api_save: {
|
||||
save();
|
||||
return true;
|
||||
}
|
||||
case R.id.menu_api_settings_revoke: {
|
||||
revokeAccess();
|
||||
return true;
|
||||
case R.id.menu_api_settings_start:
|
||||
startApp();
|
||||
}
|
||||
case R.id.menu_api_settings_advanced: {
|
||||
showAdvancedInfo();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void showAdvancedInfo() {
|
||||
String signature = null;
|
||||
// advanced info: package signature SHA-256
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
md.update(mAppSettings.getPackageSignature());
|
||||
byte[] digest = md.digest();
|
||||
signature = new String(Hex.encode(digest));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Log.e(Constants.TAG, "Should not happen!", e);
|
||||
}
|
||||
|
||||
AdvancedAppSettingsDialogFragment dialogFragment =
|
||||
AdvancedAppSettingsDialogFragment.newInstance(mAppSettings.getPackageName(), signature);
|
||||
|
||||
dialogFragment.show(getSupportFragmentManager(), "advancedDialog");
|
||||
}
|
||||
|
||||
private void startApp() {
|
||||
Intent i;
|
||||
PackageManager manager = getPackageManager();
|
||||
@@ -109,25 +185,31 @@ public class AppSettingsActivity extends ActionBarActivity {
|
||||
|
||||
private void loadData(Bundle savedInstanceState, Uri appUri) {
|
||||
mAppSettings = new ProviderHelper(this).getApiAppSettings(appUri);
|
||||
mSettingsFragment.setAppSettings(mAppSettings);
|
||||
|
||||
// get application name and icon from package manager
|
||||
String appName;
|
||||
PackageManager pm = getPackageManager();
|
||||
Drawable appIcon = null;
|
||||
PackageManager pm = getApplicationContext().getPackageManager();
|
||||
try {
|
||||
ApplicationInfo ai = pm.getApplicationInfo(mAppSettings.getPackageName(), 0);
|
||||
|
||||
appName = (String) pm.getApplicationLabel(ai);
|
||||
appIcon = pm.getApplicationIcon(ai);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
// fallback
|
||||
appName = mAppSettings.getPackageName();
|
||||
}
|
||||
setTitle(appName);
|
||||
mAppNameView.setText(appName);
|
||||
mAppIconView.setImageDrawable(appIcon);
|
||||
|
||||
Uri accountsUri = appUri.buildUpon().appendPath(KeychainContract.PATH_ACCOUNTS).build();
|
||||
Log.d(Constants.TAG, "accountsUri: " + accountsUri);
|
||||
startListFragment(savedInstanceState, accountsUri);
|
||||
Uri allowedKeysUri = appUri.buildUpon().appendPath(KeychainContract.PATH_ALLOWED_KEYS).build();
|
||||
Log.d(Constants.TAG, "allowedKeysUri: " + allowedKeysUri);
|
||||
startListFragments(savedInstanceState, accountsUri, allowedKeysUri);
|
||||
}
|
||||
|
||||
private void startListFragment(Bundle savedInstanceState, Uri dataUri) {
|
||||
private void startListFragments(Bundle savedInstanceState, Uri accountsUri, Uri allowedKeysUri) {
|
||||
// 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.
|
||||
@@ -135,13 +217,23 @@ public class AppSettingsActivity extends ActionBarActivity {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create an instance of the fragment
|
||||
mAccountsListFragment = AccountsListFragment.newInstance(dataUri);
|
||||
// show accounts only if available (deprecated API)
|
||||
Cursor cursor = getContentResolver().query(accountsUri, null, null, null, null);
|
||||
if (cursor.moveToFirst()) {
|
||||
mAccountsLabel.setVisibility(View.VISIBLE);
|
||||
mAccountsListFragment = AccountsListFragment.newInstance(accountsUri);
|
||||
// Create an instance of the fragments
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.api_accounts_list_fragment, mAccountsListFragment)
|
||||
.commitAllowingStateLoss();
|
||||
}
|
||||
|
||||
// Create an instance of the fragments
|
||||
mAllowedKeysFragment = AppSettingsAllowedKeysListFragment.newInstance(allowedKeysUri);
|
||||
// Add the fragment to the 'fragment_container' FrameLayout
|
||||
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.api_accounts_list_fragment, mAccountsListFragment)
|
||||
.replace(R.id.api_allowed_keys_list_fragment, mAllowedKeysFragment)
|
||||
.commitAllowingStateLoss();
|
||||
// do it immediately!
|
||||
getSupportFragmentManager().executePendingTransactions();
|
||||
|
||||
+292
@@ -0,0 +1,292 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.remote.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.OperationApplicationException;
|
||||
import android.database.Cursor;
|
||||
import android.database.DatabaseUtils;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ListView;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.ListFragmentWorkaround;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.SelectKeyCursorAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.widget.FixedListView;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.Vector;
|
||||
|
||||
public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||
private static final String ARG_DATA_URI = "uri";
|
||||
|
||||
private SelectKeyCursorAdapter mAdapter;
|
||||
private Set<Long> mSelectedMasterKeyIds;
|
||||
private ProviderHelper mProviderHelper;
|
||||
|
||||
private Uri mDataUri;
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static AppSettingsAllowedKeysListFragment newInstance(Uri dataUri) {
|
||||
AppSettingsAllowedKeysListFragment frag = new AppSettingsAllowedKeysListFragment();
|
||||
Bundle args = new Bundle();
|
||||
|
||||
args.putParcelable(ARG_DATA_URI, dataUri);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mProviderHelper = new ProviderHelper(getActivity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View layout = super.onCreateView(inflater, container,
|
||||
savedInstanceState);
|
||||
ListView lv = (ListView) layout.findViewById(android.R.id.list);
|
||||
ViewGroup parent = (ViewGroup) lv.getParent();
|
||||
|
||||
/*
|
||||
* http://stackoverflow.com/a/15880684
|
||||
* Remove ListView and add FixedListView in its place.
|
||||
* This is done here programatically to be still able to use the progressBar of ListFragment.
|
||||
*
|
||||
* We want FixedListView to be able to put this ListFragment inside a ScrollView
|
||||
*/
|
||||
int lvIndex = parent.indexOfChild(lv);
|
||||
parent.removeViewAt(lvIndex);
|
||||
FixedListView newLv = new FixedListView(getActivity());
|
||||
newLv.setId(android.R.id.list);
|
||||
parent.addView(newLv, lvIndex, lv.getLayoutParams());
|
||||
return layout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define Adapter and Loader on create of Activity
|
||||
*/
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
mDataUri = getArguments().getParcelable(ARG_DATA_URI);
|
||||
|
||||
getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
|
||||
|
||||
// Give some text to display if there is no data. In a real
|
||||
// application this would come from a resource.
|
||||
setEmptyText(getString(R.string.list_empty));
|
||||
|
||||
mAdapter = new SecretKeyCursorAdapter(getActivity(), null, 0, getListView());
|
||||
|
||||
setListAdapter(mAdapter);
|
||||
|
||||
// Start out with a progress indicator.
|
||||
setListShown(false);
|
||||
|
||||
mSelectedMasterKeyIds = mProviderHelper.getAllKeyIdsForApp(mDataUri);
|
||||
Log.d(Constants.TAG, "allowed: " + mSelectedMasterKeyIds.toString());
|
||||
|
||||
// Prepare the loader. Either re-connect with an existing one,
|
||||
// or start a new one.
|
||||
getLoaderManager().initLoader(0, null, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects items based on master key ids in list view
|
||||
*
|
||||
* @param masterKeyIds
|
||||
*/
|
||||
private void preselectMasterKeyIds(Set<Long> masterKeyIds) {
|
||||
for (int i = 0; i < getListView().getCount(); ++i) {
|
||||
long listKeyId = mAdapter.getMasterKeyId(i);
|
||||
for (long keyId : masterKeyIds) {
|
||||
if (listKeyId == keyId) {
|
||||
getListView().setItemChecked(i, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all selected master key ids
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Set<Long> getSelectedMasterKeyIds() {
|
||||
// mListView.getCheckedItemIds() would give the row ids of the KeyRings not the master key
|
||||
// ids!
|
||||
Set<Long> keyIds = new HashSet<>();
|
||||
for (int i = 0; i < getListView().getCount(); ++i) {
|
||||
if (getListView().isItemChecked(i)) {
|
||||
keyIds.add(mAdapter.getMasterKeyId(i));
|
||||
}
|
||||
}
|
||||
|
||||
return keyIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all selected user ids
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String[] getSelectedUserIds() {
|
||||
Vector<String> userIds = new Vector<>();
|
||||
for (int i = 0; i < getListView().getCount(); ++i) {
|
||||
if (getListView().isItemChecked(i)) {
|
||||
userIds.add(mAdapter.getUserId(i));
|
||||
}
|
||||
}
|
||||
|
||||
// make empty array to not return null
|
||||
String userIdArray[] = new String[0];
|
||||
return userIds.toArray(userIdArray);
|
||||
}
|
||||
|
||||
public void saveAllowedKeys() {
|
||||
try {
|
||||
mProviderHelper.saveAllowedKeyIdsForApp(mDataUri, getSelectedMasterKeyIds());
|
||||
} catch (RemoteException | OperationApplicationException e) {
|
||||
Log.e(Constants.TAG, "Problem saving allowed key ids!", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
Uri baseUri = KeyRings.buildUnifiedKeyRingsUri();
|
||||
|
||||
// These are the rows that we will retrieve.
|
||||
String[] projection = new String[]{
|
||||
KeyRings._ID,
|
||||
KeyRings.MASTER_KEY_ID,
|
||||
KeyRings.USER_ID,
|
||||
KeyRings.IS_EXPIRED,
|
||||
KeyRings.IS_REVOKED,
|
||||
KeyRings.HAS_ENCRYPT,
|
||||
KeyRings.VERIFIED,
|
||||
KeyRings.HAS_ANY_SECRET,
|
||||
};
|
||||
|
||||
String inMasterKeyList = null;
|
||||
if (mSelectedMasterKeyIds != null && mSelectedMasterKeyIds.size() > 0) {
|
||||
inMasterKeyList = Tables.KEYS + "." + KeyRings.MASTER_KEY_ID + " IN (";
|
||||
Iterator iter = mSelectedMasterKeyIds.iterator();
|
||||
while (iter.hasNext()) {
|
||||
inMasterKeyList += DatabaseUtils.sqlEscapeString("" + iter.next());
|
||||
if (iter.hasNext()) {
|
||||
inMasterKeyList += ", ";
|
||||
}
|
||||
}
|
||||
inMasterKeyList += ")";
|
||||
}
|
||||
|
||||
String selection = KeyRings.HAS_ANY_SECRET + " != 0";
|
||||
|
||||
String orderBy = KeyRings.USER_ID + " ASC";
|
||||
if (inMasterKeyList != null) {
|
||||
// sort by selected master keys
|
||||
orderBy = inMasterKeyList + " DESC, " + orderBy;
|
||||
}
|
||||
// Now create and return a CursorLoader that will take care of
|
||||
// creating a Cursor for the data being displayed.
|
||||
return new CursorLoader(getActivity(), baseUri, projection, selection, null, orderBy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
// Swap the new cursor in. (The framework will take care of closing the
|
||||
// old cursor once we return.)
|
||||
mAdapter.swapCursor(data);
|
||||
|
||||
// The list should now be shown.
|
||||
if (isResumed()) {
|
||||
setListShown(true);
|
||||
} else {
|
||||
setListShownNoAnimation(true);
|
||||
}
|
||||
|
||||
// preselect given master keys
|
||||
preselectMasterKeyIds(mSelectedMasterKeyIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
// This is called when the last Cursor provided to onLoadFinished()
|
||||
// above is about to be closed. We need to make sure we are no
|
||||
// longer using it.
|
||||
mAdapter.swapCursor(null);
|
||||
}
|
||||
|
||||
private class SecretKeyCursorAdapter extends SelectKeyCursorAdapter {
|
||||
|
||||
public SecretKeyCursorAdapter(Context context, Cursor c, int flags, ListView listView) {
|
||||
super(context, c, flags, listView);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initIndex(Cursor cursor) {
|
||||
super.initIndex(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
super.bindView(view, context, cursor);
|
||||
ViewHolderItem h = (ViewHolderItem) view.getTag();
|
||||
|
||||
// We care about the checkbox
|
||||
h.selected.setVisibility(View.VISIBLE);
|
||||
// the getListView works because this is not a static subclass!
|
||||
h.selected.setChecked(getListView().isItemChecked(cursor.getPosition()));
|
||||
|
||||
boolean enabled = false;
|
||||
if ((Boolean) h.statusIcon.getTag()) {
|
||||
h.statusIcon.setVisibility(View.GONE);
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
h.setEnabled(enabled);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -38,7 +38,7 @@ import org.sufficientlysecure.keychain.util.Log;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
public class AppSettingsFragment extends Fragment {
|
||||
public class AppSettingsHeaderFragment extends Fragment {
|
||||
|
||||
// model
|
||||
private AppSettings mAppSettings;
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.remote.ui;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.DrawerActivity;
|
||||
|
||||
public class AppsListActivity extends DrawerActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.api_apps_list_activity);
|
||||
|
||||
activateDrawerNavigation(savedInstanceState);
|
||||
}
|
||||
|
||||
}
|
||||
+178
-172
@@ -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
|
||||
@@ -48,82 +48,83 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
public class AppsListFragment extends ListFragment implements
|
||||
LoaderManager.LoaderCallbacks<Cursor> {
|
||||
LoaderManager.LoaderCallbacks<Cursor>, OnItemClickListener {
|
||||
|
||||
// This is the Adapter being used to display the list's data.
|
||||
RegisteredAppsAdapter mAdapter;
|
||||
AppsAdapter mAdapter;
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
getListView().setOnItemClickListener(new OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
|
||||
String selectedPackageName = mAdapter.getItemPackageName(position);
|
||||
boolean installed = mAdapter.getItemIsInstalled(position);
|
||||
boolean registered = mAdapter.getItemIsRegistered(position);
|
||||
getListView().setOnItemClickListener(this);
|
||||
|
||||
if (installed) {
|
||||
if (registered) {
|
||||
// edit app settings
|
||||
Intent intent = new Intent(getActivity(), AppSettingsActivity.class);
|
||||
intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(selectedPackageName));
|
||||
startActivity(intent);
|
||||
} else {
|
||||
Intent i;
|
||||
PackageManager manager = getActivity().getPackageManager();
|
||||
try {
|
||||
i = manager.getLaunchIntentForPackage(selectedPackageName);
|
||||
if (i == null)
|
||||
throw new PackageManager.NameNotFoundException();
|
||||
// start like the Android launcher would do
|
||||
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
|
||||
i.addCategory(Intent.CATEGORY_LAUNCHER);
|
||||
startActivity(i);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.e(Constants.TAG, "startApp", e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
startActivity(new Intent(Intent.ACTION_VIEW,
|
||||
Uri.parse("market://details?id=" + selectedPackageName)));
|
||||
} catch (ActivityNotFoundException anfe) {
|
||||
startActivity(new Intent(Intent.ACTION_VIEW,
|
||||
Uri.parse("http://play.google.com/store/apps/details?id=" + selectedPackageName)));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Give some text to display if there is no data. In a real
|
||||
// application this would come from a resource.
|
||||
setEmptyText(getString(R.string.api_no_apps));
|
||||
// NOTE: No setEmptyText(), we always have the default entries
|
||||
|
||||
// We have a menu item to show in action bar.
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
// Create an empty adapter we will use to display the loaded data.
|
||||
mAdapter = new RegisteredAppsAdapter(getActivity(), null, 0);
|
||||
mAdapter = new AppsAdapter(getActivity(), null, 0);
|
||||
setListAdapter(mAdapter);
|
||||
|
||||
// Loader is started in onResume!
|
||||
// NOTE: Loader is started in onResume!
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
// after coming back from Google Play -> reload
|
||||
|
||||
// Start out with a progress indicator.
|
||||
setListShown(false);
|
||||
|
||||
// After coming back from Google Play -> reload
|
||||
getLoaderManager().restartLoader(0, null, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
String selectedPackageName = mAdapter.getItemPackageName(position);
|
||||
boolean installed = mAdapter.getItemIsInstalled(position);
|
||||
boolean registered = mAdapter.getItemIsRegistered(position);
|
||||
|
||||
if (installed) {
|
||||
if (registered) {
|
||||
// Edit app settings
|
||||
Intent intent = new Intent(getActivity(), AppSettingsActivity.class);
|
||||
intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(selectedPackageName));
|
||||
startActivity(intent);
|
||||
} else {
|
||||
Intent i;
|
||||
PackageManager manager = getActivity().getPackageManager();
|
||||
try {
|
||||
i = manager.getLaunchIntentForPackage(selectedPackageName);
|
||||
if (i == null) {
|
||||
throw new PackageManager.NameNotFoundException();
|
||||
}
|
||||
// Start like the Android launcher would do
|
||||
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
|
||||
i.addCategory(Intent.CATEGORY_LAUNCHER);
|
||||
startActivity(i);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.e(Constants.TAG, "startApp", e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
startActivity(new Intent(Intent.ACTION_VIEW,
|
||||
Uri.parse("market://details?id=" + selectedPackageName)));
|
||||
} catch (ActivityNotFoundException anfe) {
|
||||
startActivity(new Intent(Intent.ACTION_VIEW,
|
||||
Uri.parse("https://play.google.com/store/apps/details?id=" + selectedPackageName)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final String TEMP_COLUMN_NAME = "NAME";
|
||||
private static final String TEMP_COLUMN_INSTALLED = "INSTALLED";
|
||||
private static final String TEMP_COLUMN_REGISTERED = "REGISTERED";
|
||||
private static final String TEMP_COLUMN_ICON_RES_ID = "ICON_RES_ID";
|
||||
|
||||
// These are the Contacts rows that we will retrieve.
|
||||
static final String[] PROJECTION = new String[]{
|
||||
ApiApps._ID, // 0
|
||||
ApiApps.PACKAGE_NAME, // 1
|
||||
@@ -149,106 +150,17 @@ public class AppsListFragment extends ListFragment implements
|
||||
|
||||
// Now create and return a CursorLoader that will take care of
|
||||
// creating a Cursor for the data being displayed.
|
||||
return new CursorLoader(getActivity(), baseUri, PROJECTION, null, null,
|
||||
return new AppsLoader(getActivity(), baseUri, PROJECTION, null, null,
|
||||
ApiApps.PACKAGE_NAME + " COLLATE LOCALIZED ASC");
|
||||
}
|
||||
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
MatrixCursor availableAppsCursor = new MatrixCursor(new String[]{
|
||||
ApiApps._ID,
|
||||
ApiApps.PACKAGE_NAME,
|
||||
TEMP_COLUMN_NAME,
|
||||
TEMP_COLUMN_INSTALLED,
|
||||
TEMP_COLUMN_REGISTERED,
|
||||
TEMP_COLUMN_ICON_RES_ID
|
||||
});
|
||||
// NOTE: SORT ascending by package name, this is REQUIRED for CursorJoiner!
|
||||
// Drawables taken from projects res/drawables-xxhdpi/ic_launcher.png
|
||||
availableAppsCursor.addRow(new Object[]{1, "com.fsck.k9", "K-9 Mail", 0, 0, R.drawable.apps_k9});
|
||||
availableAppsCursor.addRow(new Object[]{1, "com.zeapo.pwdstore", "Password Store", 0, 0, R.drawable.apps_password_store});
|
||||
availableAppsCursor.addRow(new Object[]{1, "eu.siacs.conversations", "Conversations (Instant Messaging)", 0, 0, R.drawable.apps_conversations});
|
||||
|
||||
MatrixCursor mergedCursor = new MatrixCursor(new String[]{
|
||||
ApiApps._ID,
|
||||
ApiApps.PACKAGE_NAME,
|
||||
TEMP_COLUMN_NAME,
|
||||
TEMP_COLUMN_INSTALLED,
|
||||
TEMP_COLUMN_REGISTERED,
|
||||
TEMP_COLUMN_ICON_RES_ID
|
||||
});
|
||||
|
||||
CursorJoiner joiner = new CursorJoiner(
|
||||
availableAppsCursor,
|
||||
new String[]{ApiApps.PACKAGE_NAME},
|
||||
data,
|
||||
new String[]{ApiApps.PACKAGE_NAME});
|
||||
for (CursorJoiner.Result joinerResult : joiner) {
|
||||
switch (joinerResult) {
|
||||
case LEFT: {
|
||||
// handle case where a row in availableAppsCursor is unique
|
||||
String packageName = availableAppsCursor.getString(INDEX_PACKAGE_NAME);
|
||||
|
||||
mergedCursor.addRow(new Object[]{
|
||||
1, // no need for unique _ID
|
||||
packageName,
|
||||
availableAppsCursor.getString(INDEX_NAME),
|
||||
isInstalled(packageName),
|
||||
0,
|
||||
availableAppsCursor.getInt(INDEX_ICON_RES_ID)
|
||||
});
|
||||
break;
|
||||
}
|
||||
case RIGHT: {
|
||||
// handle case where a row in data is unique
|
||||
String packageName = data.getString(INDEX_PACKAGE_NAME);
|
||||
|
||||
mergedCursor.addRow(new Object[]{
|
||||
1, // no need for unique _ID
|
||||
packageName,
|
||||
null,
|
||||
isInstalled(packageName),
|
||||
1, // registered!
|
||||
R.drawable.ic_launcher // icon is retrieved later
|
||||
});
|
||||
break;
|
||||
}
|
||||
case BOTH: {
|
||||
// handle case where a row with the same key is in both cursors
|
||||
String packageName = data.getString(INDEX_PACKAGE_NAME);
|
||||
|
||||
String name;
|
||||
if (isInstalled(packageName) == 1) {
|
||||
name = data.getString(INDEX_NAME);
|
||||
} else {
|
||||
// if not installed take name from available apps list
|
||||
name = availableAppsCursor.getString(INDEX_NAME);
|
||||
}
|
||||
|
||||
mergedCursor.addRow(new Object[]{
|
||||
1, // no need for unique _ID
|
||||
packageName,
|
||||
name,
|
||||
isInstalled(packageName),
|
||||
1, // registered!
|
||||
R.drawable.ic_launcher // icon is retrieved later
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Swap the new cursor in. (The framework will take care of closing the
|
||||
// old cursor once we return.)
|
||||
mAdapter.swapCursor(mergedCursor);
|
||||
}
|
||||
mAdapter.swapCursor(data);
|
||||
|
||||
private int isInstalled(String packageName) {
|
||||
try {
|
||||
getActivity().getPackageManager().getApplicationInfo(packageName, 0);
|
||||
return 1;
|
||||
} catch (final PackageManager.NameNotFoundException e) {
|
||||
return 0;
|
||||
}
|
||||
// The list should now be shown.
|
||||
setListShown(true);
|
||||
}
|
||||
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
@@ -258,12 +170,127 @@ public class AppsListFragment extends ListFragment implements
|
||||
mAdapter.swapCursor(null);
|
||||
}
|
||||
|
||||
private class RegisteredAppsAdapter extends CursorAdapter {
|
||||
/**
|
||||
* Besides the queried cursor with all registered apps, this loader also returns non-installed
|
||||
* proposed apps using a MatrixCursor.
|
||||
*/
|
||||
private static class AppsLoader extends CursorLoader {
|
||||
|
||||
public AppsLoader(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public AppsLoader(Context context, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
||||
super(context, uri, projection, selection, selectionArgs, sortOrder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor loadInBackground() {
|
||||
// Load registered apps from content provider
|
||||
Cursor data = super.loadInBackground();
|
||||
|
||||
MatrixCursor availableAppsCursor = new MatrixCursor(new String[]{
|
||||
ApiApps._ID,
|
||||
ApiApps.PACKAGE_NAME,
|
||||
TEMP_COLUMN_NAME,
|
||||
TEMP_COLUMN_INSTALLED,
|
||||
TEMP_COLUMN_REGISTERED,
|
||||
TEMP_COLUMN_ICON_RES_ID
|
||||
});
|
||||
// NOTE: SORT ascending by package name, this is REQUIRED for CursorJoiner!
|
||||
// Drawables taken from projects res/drawables-xxhdpi/ic_launcher.png
|
||||
availableAppsCursor.addRow(new Object[]{1, "com.fsck.k9", "K-9 Mail", 0, 0, R.drawable.apps_k9});
|
||||
availableAppsCursor.addRow(new Object[]{1, "com.zeapo.pwdstore", "Password Store", 0, 0, R.drawable.apps_password_store});
|
||||
availableAppsCursor.addRow(new Object[]{1, "eu.siacs.conversations", "Conversations (Instant Messaging)", 0, 0, R.drawable.apps_conversations});
|
||||
|
||||
MatrixCursor mergedCursor = new MatrixCursor(new String[]{
|
||||
ApiApps._ID,
|
||||
ApiApps.PACKAGE_NAME,
|
||||
TEMP_COLUMN_NAME,
|
||||
TEMP_COLUMN_INSTALLED,
|
||||
TEMP_COLUMN_REGISTERED,
|
||||
TEMP_COLUMN_ICON_RES_ID
|
||||
});
|
||||
|
||||
CursorJoiner joiner = new CursorJoiner(
|
||||
availableAppsCursor,
|
||||
new String[]{ApiApps.PACKAGE_NAME},
|
||||
data,
|
||||
new String[]{ApiApps.PACKAGE_NAME});
|
||||
for (CursorJoiner.Result joinerResult : joiner) {
|
||||
switch (joinerResult) {
|
||||
case LEFT: {
|
||||
// handle case where a row in availableAppsCursor is unique
|
||||
String packageName = availableAppsCursor.getString(INDEX_PACKAGE_NAME);
|
||||
|
||||
mergedCursor.addRow(new Object[]{
|
||||
1, // no need for unique _ID
|
||||
packageName,
|
||||
availableAppsCursor.getString(INDEX_NAME),
|
||||
isInstalled(packageName),
|
||||
0,
|
||||
availableAppsCursor.getInt(INDEX_ICON_RES_ID)
|
||||
});
|
||||
break;
|
||||
}
|
||||
case RIGHT: {
|
||||
// handle case where a row in data is unique
|
||||
String packageName = data.getString(INDEX_PACKAGE_NAME);
|
||||
|
||||
mergedCursor.addRow(new Object[]{
|
||||
1, // no need for unique _ID
|
||||
packageName,
|
||||
null,
|
||||
isInstalled(packageName),
|
||||
1, // registered!
|
||||
R.drawable.ic_launcher // icon is retrieved later
|
||||
});
|
||||
break;
|
||||
}
|
||||
case BOTH: {
|
||||
// handle case where a row with the same key is in both cursors
|
||||
String packageName = data.getString(INDEX_PACKAGE_NAME);
|
||||
|
||||
String name;
|
||||
if (isInstalled(packageName) == 1) {
|
||||
name = data.getString(INDEX_NAME);
|
||||
} else {
|
||||
// if not installed take name from available apps list
|
||||
name = availableAppsCursor.getString(INDEX_NAME);
|
||||
}
|
||||
|
||||
mergedCursor.addRow(new Object[]{
|
||||
1, // no need for unique _ID
|
||||
packageName,
|
||||
name,
|
||||
isInstalled(packageName),
|
||||
1, // registered!
|
||||
R.drawable.ic_launcher // icon is retrieved later
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mergedCursor;
|
||||
}
|
||||
|
||||
private int isInstalled(String packageName) {
|
||||
try {
|
||||
getContext().getPackageManager().getApplicationInfo(packageName, 0);
|
||||
return 1;
|
||||
} catch (final PackageManager.NameNotFoundException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class AppsAdapter extends CursorAdapter {
|
||||
|
||||
private LayoutInflater mInflater;
|
||||
private PackageManager mPM;
|
||||
|
||||
public RegisteredAppsAdapter(Context context, Cursor c, int flags) {
|
||||
public AppsAdapter(Context context, Cursor c, int flags) {
|
||||
super(context, c, flags);
|
||||
|
||||
mInflater = LayoutInflater.from(context);
|
||||
@@ -273,44 +300,23 @@ public class AppsListFragment extends ListFragment implements
|
||||
/**
|
||||
* Similar to CursorAdapter.getItemId().
|
||||
* Required to build Uris for api apps, which are not based on row ids
|
||||
*
|
||||
* @param position
|
||||
* @return
|
||||
*/
|
||||
public String getItemPackageName(int position) {
|
||||
if (mDataValid && mCursor != null) {
|
||||
if (mCursor.moveToPosition(position)) {
|
||||
return mCursor.getString(INDEX_PACKAGE_NAME);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
if (mDataValid && mCursor != null && mCursor.moveToPosition(position)) {
|
||||
return mCursor.getString(INDEX_PACKAGE_NAME);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean getItemIsInstalled(int position) {
|
||||
if (mDataValid && mCursor != null) {
|
||||
if (mCursor.moveToPosition(position)) {
|
||||
return (mCursor.getInt(INDEX_INSTALLED) == 1);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return mDataValid && mCursor != null
|
||||
&& mCursor.moveToPosition(position) && (mCursor.getInt(INDEX_INSTALLED) == 1);
|
||||
}
|
||||
|
||||
public boolean getItemIsRegistered(int position) {
|
||||
if (mDataValid && mCursor != null) {
|
||||
if (mCursor.moveToPosition(position)) {
|
||||
return (mCursor.getInt(INDEX_REGISTERED) == 1);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return mDataValid && mCursor != null
|
||||
&& mCursor.moveToPosition(position) && (mCursor.getInt(INDEX_REGISTERED) == 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+214
-200
@@ -22,7 +22,6 @@ import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.SpannableStringBuilder;
|
||||
@@ -39,14 +38,14 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.remote.AccountSettings;
|
||||
import org.sufficientlysecure.keychain.remote.AppSettings;
|
||||
import org.sufficientlysecure.keychain.ui.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.ui.SelectPublicKeyFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.ActionBarHelper;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class RemoteServiceActivity extends ActionBarActivity {
|
||||
public class RemoteServiceActivity extends BaseActivity {
|
||||
|
||||
public static final String ACTION_REGISTER = Constants.INTENT_PREFIX + "API_ACTIVITY_REGISTER";
|
||||
public static final String ACTION_CREATE_ACCOUNT = Constants.INTENT_PREFIX
|
||||
@@ -76,7 +75,7 @@ public class RemoteServiceActivity extends ActionBarActivity {
|
||||
public static final String EXTRA_ERROR_MESSAGE = "error_message";
|
||||
|
||||
// register view
|
||||
private AppSettingsFragment mAppSettingsFragment;
|
||||
private AppSettingsHeaderFragment mAppSettingsHeaderFragment;
|
||||
// create acc view
|
||||
private AccountSettingsFragment mAccSettingsFragment;
|
||||
// select pub keys view
|
||||
@@ -96,235 +95,250 @@ public class RemoteServiceActivity extends ActionBarActivity {
|
||||
handleActions(getIntent(), savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initLayout() {
|
||||
// done in handleActions()
|
||||
}
|
||||
|
||||
protected void handleActions(Intent intent, Bundle savedInstanceState) {
|
||||
|
||||
String action = intent.getAction();
|
||||
final Bundle extras = intent.getExtras();
|
||||
|
||||
|
||||
if (ACTION_REGISTER.equals(action)) {
|
||||
final String packageName = extras.getString(EXTRA_PACKAGE_NAME);
|
||||
final byte[] packageSignature = extras.getByteArray(EXTRA_PACKAGE_SIGNATURE);
|
||||
Log.d(Constants.TAG, "ACTION_REGISTER packageName: " + packageName);
|
||||
switch (action) {
|
||||
case ACTION_REGISTER: {
|
||||
final String packageName = extras.getString(EXTRA_PACKAGE_NAME);
|
||||
final byte[] packageSignature = extras.getByteArray(EXTRA_PACKAGE_SIGNATURE);
|
||||
Log.d(Constants.TAG, "ACTION_REGISTER packageName: " + packageName);
|
||||
|
||||
setContentView(R.layout.api_remote_register_app);
|
||||
setContentView(R.layout.api_remote_register_app);
|
||||
initToolbar();
|
||||
|
||||
mAppSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById(
|
||||
R.id.api_app_settings_fragment);
|
||||
mAppSettingsHeaderFragment = (AppSettingsHeaderFragment) getSupportFragmentManager().findFragmentById(
|
||||
R.id.api_app_settings_fragment);
|
||||
|
||||
AppSettings settings = new AppSettings(packageName, packageSignature);
|
||||
mAppSettingsFragment.setAppSettings(settings);
|
||||
AppSettings settings = new AppSettings(packageName, packageSignature);
|
||||
mAppSettingsHeaderFragment.setAppSettings(settings);
|
||||
|
||||
// Inflate a "Done"/"Cancel" custom action bar view
|
||||
ActionBarHelper.setTwoButtonView(getSupportActionBar(),
|
||||
R.string.api_register_allow, R.drawable.ic_action_done,
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// Allow
|
||||
// Inflate a "Done"/"Cancel" custom action bar view
|
||||
setFullScreenDialogTwoButtons(
|
||||
R.string.api_register_allow, R.drawable.ic_check_white_24dp,
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// Allow
|
||||
|
||||
mProviderHelper.insertApiApp(mAppSettingsFragment.getAppSettings());
|
||||
|
||||
// give data through for new service call
|
||||
Intent resultData = extras.getParcelable(EXTRA_DATA);
|
||||
RemoteServiceActivity.this.setResult(RESULT_OK, resultData);
|
||||
RemoteServiceActivity.this.finish();
|
||||
}
|
||||
}, R.string.api_register_disallow, R.drawable.ic_action_cancel,
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// Disallow
|
||||
RemoteServiceActivity.this.setResult(RESULT_CANCELED);
|
||||
RemoteServiceActivity.this.finish();
|
||||
}
|
||||
}
|
||||
);
|
||||
} else if (ACTION_CREATE_ACCOUNT.equals(action)) {
|
||||
final String packageName = extras.getString(EXTRA_PACKAGE_NAME);
|
||||
final String accName = extras.getString(EXTRA_ACC_NAME);
|
||||
|
||||
setContentView(R.layout.api_remote_create_account);
|
||||
|
||||
mAccSettingsFragment = (AccountSettingsFragment) getSupportFragmentManager().findFragmentById(
|
||||
R.id.api_account_settings_fragment);
|
||||
|
||||
TextView text = (TextView) findViewById(R.id.api_remote_create_account_text);
|
||||
|
||||
// update existing?
|
||||
Uri uri = KeychainContract.ApiAccounts.buildByPackageAndAccountUri(packageName, accName);
|
||||
AccountSettings settings = mProviderHelper.getApiAccountSettings(uri);
|
||||
if (settings == null) {
|
||||
// create new account
|
||||
settings = new AccountSettings(accName);
|
||||
mUpdateExistingAccount = false;
|
||||
|
||||
text.setText(R.string.api_create_account_text);
|
||||
} else {
|
||||
// update existing account
|
||||
mUpdateExistingAccount = true;
|
||||
|
||||
text.setText(R.string.api_update_account_text);
|
||||
}
|
||||
mAccSettingsFragment.setAccSettings(settings);
|
||||
|
||||
// Inflate a "Done"/"Cancel" custom action bar view
|
||||
ActionBarHelper.setTwoButtonView(getSupportActionBar(),
|
||||
R.string.api_settings_save, R.drawable.ic_action_done,
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// Save
|
||||
|
||||
// user needs to select a key if it has explicitly requested (None is only allowed for new accounts)
|
||||
if (mUpdateExistingAccount && mAccSettingsFragment.getAccSettings().getKeyId() == Constants.key.none) {
|
||||
Notify.showNotify(RemoteServiceActivity.this, getString(R.string.api_register_error_select_key), Notify.Style.ERROR);
|
||||
} else {
|
||||
if (mUpdateExistingAccount) {
|
||||
Uri baseUri = KeychainContract.ApiAccounts.buildBaseUri(packageName);
|
||||
Uri accountUri = baseUri.buildUpon().appendEncodedPath(accName).build();
|
||||
mProviderHelper.updateApiAccount(
|
||||
accountUri,
|
||||
mAccSettingsFragment.getAccSettings());
|
||||
} else {
|
||||
mProviderHelper.insertApiAccount(
|
||||
KeychainContract.ApiAccounts.buildBaseUri(packageName),
|
||||
mAccSettingsFragment.getAccSettings());
|
||||
}
|
||||
mProviderHelper.insertApiApp(mAppSettingsHeaderFragment.getAppSettings());
|
||||
|
||||
// give data through for new service call
|
||||
Intent resultData = extras.getParcelable(EXTRA_DATA);
|
||||
RemoteServiceActivity.this.setResult(RESULT_OK, resultData);
|
||||
RemoteServiceActivity.this.finish();
|
||||
}
|
||||
}, R.string.api_register_disallow, R.drawable.ic_close_white_24dp,
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// Disallow
|
||||
RemoteServiceActivity.this.setResult(RESULT_CANCELED);
|
||||
RemoteServiceActivity.this.finish();
|
||||
}
|
||||
}
|
||||
}, R.string.api_settings_cancel, R.drawable.ic_action_cancel,
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// Cancel
|
||||
RemoteServiceActivity.this.setResult(RESULT_CANCELED);
|
||||
RemoteServiceActivity.this.finish();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
} else if (ACTION_SELECT_PUB_KEYS.equals(action)) {
|
||||
long[] selectedMasterKeyIds = intent.getLongArrayExtra(EXTRA_SELECTED_MASTER_KEY_IDS);
|
||||
boolean noUserIdsCheck = intent.getBooleanExtra(EXTRA_NO_USER_IDS_CHECK, true);
|
||||
ArrayList<String> missingUserIds = intent
|
||||
.getStringArrayListExtra(EXTRA_MISSING_USER_IDS);
|
||||
ArrayList<String> dublicateUserIds = intent
|
||||
.getStringArrayListExtra(EXTRA_DUPLICATE_USER_IDS);
|
||||
|
||||
SpannableStringBuilder ssb = new SpannableStringBuilder();
|
||||
final SpannableString textIntro = new SpannableString(
|
||||
noUserIdsCheck ? getString(R.string.api_select_pub_keys_text_no_user_ids)
|
||||
: getString(R.string.api_select_pub_keys_text)
|
||||
);
|
||||
textIntro.setSpan(new StyleSpan(Typeface.BOLD), 0, textIntro.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
ssb.append(textIntro);
|
||||
|
||||
if (missingUserIds != null && missingUserIds.size() > 0) {
|
||||
ssb.append("\n\n");
|
||||
ssb.append(getString(R.string.api_select_pub_keys_missing_text));
|
||||
ssb.append("\n");
|
||||
for (String userId : missingUserIds) {
|
||||
SpannableString ss = new SpannableString(userId + "\n");
|
||||
ss.setSpan(new BulletSpan(15, Color.BLACK), 0, ss.length(),
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
ssb.append(ss);
|
||||
}
|
||||
);
|
||||
break;
|
||||
}
|
||||
if (dublicateUserIds != null && dublicateUserIds.size() > 0) {
|
||||
ssb.append("\n\n");
|
||||
ssb.append(getString(R.string.api_select_pub_keys_dublicates_text));
|
||||
ssb.append("\n");
|
||||
for (String userId : dublicateUserIds) {
|
||||
SpannableString ss = new SpannableString(userId + "\n");
|
||||
ss.setSpan(new BulletSpan(15, Color.BLACK), 0, ss.length(),
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
ssb.append(ss);
|
||||
case ACTION_CREATE_ACCOUNT: {
|
||||
final String packageName = extras.getString(EXTRA_PACKAGE_NAME);
|
||||
final String accName = extras.getString(EXTRA_ACC_NAME);
|
||||
|
||||
setContentView(R.layout.api_remote_create_account);
|
||||
initToolbar();
|
||||
|
||||
mAccSettingsFragment = (AccountSettingsFragment) getSupportFragmentManager().findFragmentById(
|
||||
R.id.api_account_settings_fragment);
|
||||
|
||||
TextView text = (TextView) findViewById(R.id.api_remote_create_account_text);
|
||||
|
||||
// update existing?
|
||||
Uri uri = KeychainContract.ApiAccounts.buildByPackageAndAccountUri(packageName, accName);
|
||||
AccountSettings settings = mProviderHelper.getApiAccountSettings(uri);
|
||||
if (settings == null) {
|
||||
// create new account
|
||||
settings = new AccountSettings(accName);
|
||||
mUpdateExistingAccount = false;
|
||||
|
||||
text.setText(R.string.api_create_account_text);
|
||||
} else {
|
||||
// update existing account
|
||||
mUpdateExistingAccount = true;
|
||||
|
||||
text.setText(R.string.api_update_account_text);
|
||||
}
|
||||
mAccSettingsFragment.setAccSettings(settings);
|
||||
|
||||
// Inflate a "Done"/"Cancel" custom action bar view
|
||||
setFullScreenDialogDoneClose(R.string.api_settings_save,
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// Save
|
||||
|
||||
// user needs to select a key if it has explicitly requested (None is only allowed for new accounts)
|
||||
if (mUpdateExistingAccount && mAccSettingsFragment.getAccSettings().getKeyId() == Constants.key.none) {
|
||||
Notify.showNotify(RemoteServiceActivity.this, getString(R.string.api_register_error_select_key), Notify.Style.ERROR);
|
||||
} else {
|
||||
if (mUpdateExistingAccount) {
|
||||
Uri baseUri = KeychainContract.ApiAccounts.buildBaseUri(packageName);
|
||||
Uri accountUri = baseUri.buildUpon().appendEncodedPath(accName).build();
|
||||
mProviderHelper.updateApiAccount(
|
||||
accountUri,
|
||||
mAccSettingsFragment.getAccSettings());
|
||||
} else {
|
||||
mProviderHelper.insertApiAccount(
|
||||
KeychainContract.ApiAccounts.buildBaseUri(packageName),
|
||||
mAccSettingsFragment.getAccSettings());
|
||||
}
|
||||
|
||||
// give data through for new service call
|
||||
Intent resultData = extras.getParcelable(EXTRA_DATA);
|
||||
RemoteServiceActivity.this.setResult(RESULT_OK, resultData);
|
||||
RemoteServiceActivity.this.finish();
|
||||
}
|
||||
}
|
||||
},
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// Cancel
|
||||
RemoteServiceActivity.this.setResult(RESULT_CANCELED);
|
||||
RemoteServiceActivity.this.finish();
|
||||
}
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
case ACTION_SELECT_PUB_KEYS: {
|
||||
long[] selectedMasterKeyIds = intent.getLongArrayExtra(EXTRA_SELECTED_MASTER_KEY_IDS);
|
||||
boolean noUserIdsCheck = intent.getBooleanExtra(EXTRA_NO_USER_IDS_CHECK, true);
|
||||
ArrayList<String> missingUserIds = intent
|
||||
.getStringArrayListExtra(EXTRA_MISSING_USER_IDS);
|
||||
ArrayList<String> dublicateUserIds = intent
|
||||
.getStringArrayListExtra(EXTRA_DUPLICATE_USER_IDS);
|
||||
|
||||
// Inflate a "Done"/"Cancel" custom action bar view
|
||||
ActionBarHelper.setTwoButtonView(getSupportActionBar(),
|
||||
R.string.btn_okay, R.drawable.ic_action_done,
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// add key ids to params Bundle for new request
|
||||
Intent resultData = extras.getParcelable(EXTRA_DATA);
|
||||
resultData.putExtra(OpenPgpApi.EXTRA_KEY_IDS,
|
||||
mSelectFragment.getSelectedMasterKeyIds());
|
||||
SpannableStringBuilder ssb = new SpannableStringBuilder();
|
||||
final SpannableString textIntro = new SpannableString(
|
||||
noUserIdsCheck ? getString(R.string.api_select_pub_keys_text_no_user_ids)
|
||||
: getString(R.string.api_select_pub_keys_text)
|
||||
);
|
||||
textIntro.setSpan(new StyleSpan(Typeface.BOLD), 0, textIntro.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
ssb.append(textIntro);
|
||||
|
||||
RemoteServiceActivity.this.setResult(RESULT_OK, resultData);
|
||||
RemoteServiceActivity.this.finish();
|
||||
}
|
||||
}, R.string.btn_do_not_save, R.drawable.ic_action_cancel, new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// cancel
|
||||
RemoteServiceActivity.this.setResult(RESULT_CANCELED);
|
||||
RemoteServiceActivity.this.finish();
|
||||
}
|
||||
if (missingUserIds != null && missingUserIds.size() > 0) {
|
||||
ssb.append("\n\n");
|
||||
ssb.append(getString(R.string.api_select_pub_keys_missing_text));
|
||||
ssb.append("\n");
|
||||
for (String userId : missingUserIds) {
|
||||
SpannableString ss = new SpannableString(userId + "\n");
|
||||
ss.setSpan(new BulletSpan(15, Color.BLACK), 0, ss.length(),
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
ssb.append(ss);
|
||||
}
|
||||
}
|
||||
if (dublicateUserIds != null && dublicateUserIds.size() > 0) {
|
||||
ssb.append("\n\n");
|
||||
ssb.append(getString(R.string.api_select_pub_keys_dublicates_text));
|
||||
ssb.append("\n");
|
||||
for (String userId : dublicateUserIds) {
|
||||
SpannableString ss = new SpannableString(userId + "\n");
|
||||
ss.setSpan(new BulletSpan(15, Color.BLACK), 0, ss.length(),
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
ssb.append(ss);
|
||||
}
|
||||
);
|
||||
|
||||
setContentView(R.layout.api_remote_select_pub_keys);
|
||||
|
||||
// set text on view
|
||||
TextView textView = (TextView) findViewById(R.id.api_select_pub_keys_text);
|
||||
textView.setText(ssb, TextView.BufferType.SPANNABLE);
|
||||
|
||||
/* Load select pub keys fragment */
|
||||
// Check that the activity is using the layout version with
|
||||
// the fragment_container FrameLayout
|
||||
if (findViewById(R.id.api_select_pub_keys_fragment_container) != null) {
|
||||
|
||||
// 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 (savedInstanceState != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create an instance of the fragment
|
||||
mSelectFragment = SelectPublicKeyFragment.newInstance(selectedMasterKeyIds);
|
||||
setContentView(R.layout.api_remote_select_pub_keys);
|
||||
initToolbar();
|
||||
|
||||
// Add the fragment to the 'fragment_container' FrameLayout
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(R.id.api_select_pub_keys_fragment_container, mSelectFragment).commit();
|
||||
}
|
||||
} else if (ACTION_ERROR_MESSAGE.equals(action)) {
|
||||
String errorMessage = intent.getStringExtra(EXTRA_ERROR_MESSAGE);
|
||||
// Inflate a "Done"/"Cancel" custom action bar view
|
||||
setFullScreenDialogDoneClose(R.string.btn_okay,
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// add key ids to params Bundle for new request
|
||||
Intent resultData = extras.getParcelable(EXTRA_DATA);
|
||||
resultData.putExtra(OpenPgpApi.EXTRA_KEY_IDS,
|
||||
mSelectFragment.getSelectedMasterKeyIds());
|
||||
|
||||
Spannable redErrorMessage = new SpannableString(errorMessage);
|
||||
redErrorMessage.setSpan(new ForegroundColorSpan(Color.RED), 0, errorMessage.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
RemoteServiceActivity.this.setResult(RESULT_OK, resultData);
|
||||
RemoteServiceActivity.this.finish();
|
||||
}
|
||||
},
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// cancel
|
||||
RemoteServiceActivity.this.setResult(RESULT_CANCELED);
|
||||
RemoteServiceActivity.this.finish();
|
||||
}
|
||||
});
|
||||
|
||||
// Inflate a "Done" custom action bar view
|
||||
ActionBarHelper.setOneButtonView(getSupportActionBar(),
|
||||
R.string.btn_okay, R.drawable.ic_action_done,
|
||||
new View.OnClickListener() {
|
||||
// set text on view
|
||||
TextView textView = (TextView) findViewById(R.id.api_select_pub_keys_text);
|
||||
textView.setText(ssb, TextView.BufferType.SPANNABLE);
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
RemoteServiceActivity.this.setResult(RESULT_CANCELED);
|
||||
RemoteServiceActivity.this.finish();
|
||||
}
|
||||
/* Load select pub keys fragment */
|
||||
// Check that the activity is using the layout version with
|
||||
// the fragment_container FrameLayout
|
||||
if (findViewById(R.id.api_select_pub_keys_fragment_container) != null) {
|
||||
|
||||
// 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 (savedInstanceState != null) {
|
||||
return;
|
||||
}
|
||||
);
|
||||
|
||||
setContentView(R.layout.api_remote_error_message);
|
||||
// Create an instance of the fragment
|
||||
mSelectFragment = SelectPublicKeyFragment.newInstance(selectedMasterKeyIds);
|
||||
|
||||
// set text on view
|
||||
TextView textView = (TextView) findViewById(R.id.api_app_error_message_text);
|
||||
textView.setText(redErrorMessage);
|
||||
} else {
|
||||
Log.e(Constants.TAG, "Action does not exist!");
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
// Add the fragment to the 'fragment_container' FrameLayout
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(R.id.api_select_pub_keys_fragment_container, mSelectFragment).commit();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ACTION_ERROR_MESSAGE: {
|
||||
String errorMessage = intent.getStringExtra(EXTRA_ERROR_MESSAGE);
|
||||
|
||||
Spannable redErrorMessage = new SpannableString(errorMessage);
|
||||
redErrorMessage.setSpan(new ForegroundColorSpan(Color.RED), 0, errorMessage.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
|
||||
setContentView(R.layout.api_remote_error_message);
|
||||
initToolbar();
|
||||
|
||||
// Inflate a "Done" custom action bar view
|
||||
setFullScreenDialogClose(
|
||||
new View.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
RemoteServiceActivity.this.setResult(RESULT_CANCELED);
|
||||
RemoteServiceActivity.this.finish();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// set text on view
|
||||
TextView textView = (TextView) findViewById(R.id.api_app_error_message_text);
|
||||
textView.setText(redErrorMessage);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Log.e(Constants.TAG, "Action does not exist!");
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ public class CertifyActionsParcel implements Parcelable {
|
||||
final public long mMasterKeyId;
|
||||
public CertifyLevel mLevel;
|
||||
|
||||
public ArrayList<CertifyAction> mCertifyActions = new ArrayList<CertifyAction>();
|
||||
public ArrayList<CertifyAction> mCertifyActions = new ArrayList<>();
|
||||
|
||||
public CertifyActionsParcel(long masterKeyId) {
|
||||
mMasterKeyId = masterKeyId;
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ public class ContactSyncAdapterService extends Service {
|
||||
// new Handler.Callback() {
|
||||
// @Override
|
||||
// public boolean handleMessage(Message msg) {
|
||||
// Bundle data = msg.getData();
|
||||
// Bundle data = msg.getInputData();
|
||||
// switch (msg.arg1) {
|
||||
// case KeychainIntentServiceHandler.MESSAGE_OKAY:
|
||||
// Log.d(Constants.TAG, "Syncing... Done.");
|
||||
|
||||
+329
-300
@@ -26,10 +26,25 @@ import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import com.textuality.keybase.lib.Proof;
|
||||
import com.textuality.keybase.lib.prover.Prover;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.spongycastle.openpgp.PGPUtil;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
|
||||
import org.sufficientlysecure.keychain.keyimport.Keyserver;
|
||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||
import org.sufficientlysecure.keychain.operations.CertifyOperation;
|
||||
import org.sufficientlysecure.keychain.operations.DeleteOperation;
|
||||
import org.sufficientlysecure.keychain.operations.EditKeyOperation;
|
||||
import org.sufficientlysecure.keychain.operations.ImportExportOperation;
|
||||
import org.sufficientlysecure.keychain.operations.PromoteKeyOperation;
|
||||
import org.sufficientlysecure.keychain.operations.SignEncryptOperation;
|
||||
import org.sufficientlysecure.keychain.operations.results.CertifyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.ConsolidateResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.DeleteResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.ExportResult;
|
||||
@@ -41,35 +56,40 @@ import org.sufficientlysecure.keychain.util.Preferences;
|
||||
import org.sufficientlysecure.keychain.keyimport.HkpKeyserver;
|
||||
import org.sufficientlysecure.keychain.keyimport.Keyserver;
|
||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
|
||||
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
import org.sufficientlysecure.keychain.operations.ImportExportOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt;
|
||||
import org.sufficientlysecure.keychain.pgp.Progressable;
|
||||
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException;
|
||||
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.ConsolidateResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
|
||||
import org.sufficientlysecure.keychain.util.ParcelableFileCache;
|
||||
import org.sufficientlysecure.keychain.util.FileHelper;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.ParcelableFileCache;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import de.measite.minidns.Client;
|
||||
import de.measite.minidns.DNSMessage;
|
||||
import de.measite.minidns.Question;
|
||||
import de.measite.minidns.Record;
|
||||
import de.measite.minidns.record.Data;
|
||||
import de.measite.minidns.record.TXT;
|
||||
|
||||
/**
|
||||
* This Service contains all important long lasting operations for OpenKeychain. It receives Intents with
|
||||
* data from the activities or other apps, queues these intents, executes them, and stops itself
|
||||
@@ -86,10 +106,14 @@ public class KeychainIntentService extends IntentService implements Progressable
|
||||
|
||||
public static final String ACTION_DECRYPT_VERIFY = Constants.INTENT_PREFIX + "DECRYPT_VERIFY";
|
||||
|
||||
public static final String ACTION_VERIFY_KEYBASE_PROOF = Constants.INTENT_PREFIX + "VERIFY_KEYBASE_PROOF";
|
||||
|
||||
public static final String ACTION_DECRYPT_METADATA = Constants.INTENT_PREFIX + "DECRYPT_METADATA";
|
||||
|
||||
public static final String ACTION_EDIT_KEYRING = Constants.INTENT_PREFIX + "EDIT_KEYRING";
|
||||
|
||||
public static final String ACTION_PROMOTE_KEYRING = Constants.INTENT_PREFIX + "PROMOTE_KEYRING";
|
||||
|
||||
public static final String ACTION_IMPORT_KEYRING = Constants.INTENT_PREFIX + "IMPORT_KEYRING";
|
||||
public static final String ACTION_EXPORT_KEYRING = Constants.INTENT_PREFIX + "EXPORT_KEYRING";
|
||||
|
||||
@@ -108,33 +132,25 @@ public class KeychainIntentService extends IntentService implements Progressable
|
||||
// encrypt, decrypt, import export
|
||||
public static final String TARGET = "target";
|
||||
public static final String SOURCE = "source";
|
||||
|
||||
// possible targets:
|
||||
public static final int IO_BYTES = 1;
|
||||
public static final int IO_URI = 2;
|
||||
public static final int IO_URIS = 3;
|
||||
|
||||
public static final String SELECTED_URI = "selected_uri";
|
||||
|
||||
// encrypt
|
||||
public static final String ENCRYPT_SIGNATURE_MASTER_ID = "secret_key_id";
|
||||
public static final String ENCRYPT_SIGNATURE_KEY_PASSPHRASE = "secret_key_passphrase";
|
||||
public static final String ENCRYPT_SIGNATURE_NFC_TIMESTAMP = "signature_nfc_timestamp";
|
||||
public static final String ENCRYPT_SIGNATURE_NFC_HASH = "signature_nfc_hash";
|
||||
public static final String ENCRYPT_USE_ASCII_ARMOR = "use_ascii_armor";
|
||||
public static final String ENCRYPT_ENCRYPTION_KEYS_IDS = "encryption_keys_ids";
|
||||
public static final String ENCRYPT_COMPRESSION_ID = "compression_id";
|
||||
public static final String ENCRYPT_MESSAGE_BYTES = "message_bytes";
|
||||
public static final String ENCRYPT_DECRYPT_INPUT_URI = "input_uri";
|
||||
public static final String ENCRYPT_INPUT_URIS = "input_uris";
|
||||
public static final String ENCRYPT_DECRYPT_OUTPUT_URI = "output_uri";
|
||||
public static final String ENCRYPT_OUTPUT_URIS = "output_uris";
|
||||
public static final String ENCRYPT_SYMMETRIC_PASSPHRASE = "passphrase";
|
||||
public static final String SIGN_ENCRYPT_PARCEL = "sign_encrypt_parcel";
|
||||
|
||||
// decrypt/verify
|
||||
public static final String DECRYPT_CIPHERTEXT_BYTES = "ciphertext_bytes";
|
||||
public static final String DECRYPT_PASSPHRASE = "passphrase";
|
||||
public static final String DECRYPT_NFC_DECRYPTED_SESSION_KEY = "nfc_decrypted_session_key";
|
||||
|
||||
// keybase proof
|
||||
public static final String KEYBASE_REQUIRED_FINGERPRINT = "keybase_required_fingerprint";
|
||||
public static final String KEYBASE_PROOF = "keybase_proof";
|
||||
|
||||
// save keyring
|
||||
public static final String EDIT_KEYRING_PARCEL = "save_parcel";
|
||||
public static final String EDIT_KEYRING_PASSPHRASE = "passphrase";
|
||||
@@ -160,6 +176,10 @@ public class KeychainIntentService extends IntentService implements Progressable
|
||||
// certify key
|
||||
public static final String CERTIFY_PARCEL = "certify_parcel";
|
||||
|
||||
// promote key
|
||||
public static final String PROMOTE_MASTER_KEY_ID = "promote_master_key_id";
|
||||
public static final String PROMOTE_TYPE = "promote_type";
|
||||
|
||||
// consolidate
|
||||
public static final String CONSOLIDATE_RECOVERY = "consolidate_recovery";
|
||||
|
||||
@@ -168,9 +188,6 @@ public class KeychainIntentService extends IntentService implements Progressable
|
||||
* possible data keys as result send over messenger
|
||||
*/
|
||||
|
||||
// encrypt
|
||||
public static final String RESULT_BYTES = "encrypted_data";
|
||||
|
||||
// decrypt/verify
|
||||
public static final String RESULT_DECRYPTED_BYTES = "decrypted_data";
|
||||
|
||||
@@ -223,303 +240,357 @@ public class KeychainIntentService extends IntentService implements Progressable
|
||||
String action = intent.getAction();
|
||||
|
||||
// executeServiceMethod action from extra bundle
|
||||
if (ACTION_CERTIFY_KEYRING.equals(action)) {
|
||||
switch (action) {
|
||||
case ACTION_CERTIFY_KEYRING: {
|
||||
|
||||
// Input
|
||||
CertifyActionsParcel parcel = data.getParcelable(CERTIFY_PARCEL);
|
||||
String keyServerUri = data.getString(UPLOAD_KEY_SERVER);
|
||||
// Input
|
||||
CertifyActionsParcel parcel = data.getParcelable(CERTIFY_PARCEL);
|
||||
String keyServerUri = data.getString(UPLOAD_KEY_SERVER);
|
||||
|
||||
// Operation
|
||||
CertifyOperation op = new CertifyOperation(this, providerHelper, this, mActionCanceled);
|
||||
CertifyResult result = op.certify(parcel, keyServerUri);
|
||||
// Operation
|
||||
CertifyOperation op = new CertifyOperation(this, providerHelper, this, mActionCanceled);
|
||||
CertifyResult result = op.certify(parcel, keyServerUri);
|
||||
|
||||
// Result
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
|
||||
// Result
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
|
||||
|
||||
} else if (ACTION_CONSOLIDATE.equals(action)) {
|
||||
|
||||
// Operation
|
||||
ConsolidateResult result;
|
||||
if (data.containsKey(CONSOLIDATE_RECOVERY) && data.getBoolean(CONSOLIDATE_RECOVERY)) {
|
||||
result = new ProviderHelper(this).consolidateDatabaseStep2(this);
|
||||
} else {
|
||||
result = new ProviderHelper(this).consolidateDatabaseStep1(this);
|
||||
break;
|
||||
}
|
||||
case ACTION_CONSOLIDATE: {
|
||||
|
||||
// Result
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
|
||||
// Operation
|
||||
ConsolidateResult result;
|
||||
if (data.containsKey(CONSOLIDATE_RECOVERY) && data.getBoolean(CONSOLIDATE_RECOVERY)) {
|
||||
result = new ProviderHelper(this).consolidateDatabaseStep2(this);
|
||||
} else {
|
||||
result = new ProviderHelper(this).consolidateDatabaseStep1(this);
|
||||
}
|
||||
|
||||
} else if (ACTION_DECRYPT_METADATA.equals(action)) {
|
||||
// Result
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
|
||||
|
||||
try {
|
||||
break;
|
||||
}
|
||||
case ACTION_DECRYPT_METADATA: {
|
||||
|
||||
try {
|
||||
/* Input */
|
||||
String passphrase = data.getString(DECRYPT_PASSPHRASE);
|
||||
byte[] nfcDecryptedSessionKey = data.getByteArray(DECRYPT_NFC_DECRYPTED_SESSION_KEY);
|
||||
String passphrase = data.getString(DECRYPT_PASSPHRASE);
|
||||
byte[] nfcDecryptedSessionKey = data.getByteArray(DECRYPT_NFC_DECRYPTED_SESSION_KEY);
|
||||
|
||||
InputData inputData = createDecryptInputData(data);
|
||||
InputData inputData = createDecryptInputData(data);
|
||||
|
||||
/* Operation */
|
||||
|
||||
Bundle resultData = new Bundle();
|
||||
Bundle resultData = new Bundle();
|
||||
|
||||
// verifyText and decrypt returning additional resultData values for the
|
||||
// verification of signatures
|
||||
PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(
|
||||
this, new ProviderHelper(this), this, inputData, null
|
||||
);
|
||||
builder.setAllowSymmetricDecryption(true)
|
||||
.setPassphrase(passphrase)
|
||||
.setDecryptMetadataOnly(true)
|
||||
.setNfcState(nfcDecryptedSessionKey);
|
||||
// verifyText and decrypt returning additional resultData values for the
|
||||
// verification of signatures
|
||||
PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(
|
||||
this, new ProviderHelper(this), this, inputData, null
|
||||
);
|
||||
builder.setAllowSymmetricDecryption(true)
|
||||
.setPassphrase(passphrase)
|
||||
.setDecryptMetadataOnly(true)
|
||||
.setNfcState(nfcDecryptedSessionKey);
|
||||
|
||||
DecryptVerifyResult decryptVerifyResult = builder.build().execute();
|
||||
DecryptVerifyResult decryptVerifyResult = builder.build().execute();
|
||||
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, decryptVerifyResult);
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, decryptVerifyResult);
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ACTION_VERIFY_KEYBASE_PROOF: {
|
||||
|
||||
} else if (ACTION_DECRYPT_VERIFY.equals(action)) {
|
||||
try {
|
||||
Proof proof = new Proof(new JSONObject(data.getString(KEYBASE_PROOF)));
|
||||
setProgress(R.string.keybase_message_fetching_data, 0, 100);
|
||||
|
||||
try {
|
||||
Prover prover = Prover.findProverFor(proof);
|
||||
|
||||
if (prover == null) {
|
||||
sendProofError(getString(R.string.keybase_no_prover_found) + ": " + proof.getPrettyName());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!prover.fetchProofData()) {
|
||||
sendProofError(prover.getLog(), getString(R.string.keybase_problem_fetching_evidence));
|
||||
return;
|
||||
}
|
||||
String requiredFingerprint = data.getString(KEYBASE_REQUIRED_FINGERPRINT);
|
||||
if (!prover.checkFingerprint(requiredFingerprint)) {
|
||||
sendProofError(getString(R.string.keybase_key_mismatch));
|
||||
return;
|
||||
}
|
||||
|
||||
String domain = prover.dnsTxtCheckRequired();
|
||||
if (domain != null) {
|
||||
DNSMessage dnsQuery = new Client().query(new Question(domain, Record.TYPE.TXT));
|
||||
if (dnsQuery == null) {
|
||||
sendProofError(prover.getLog(), getString(R.string.keybase_dns_query_failure));
|
||||
return;
|
||||
}
|
||||
Record[] records = dnsQuery.getAnswers();
|
||||
List<List<byte[]>> extents = new ArrayList<List<byte[]>>();
|
||||
for (Record r : records) {
|
||||
Data d = r.getPayload();
|
||||
if (d instanceof TXT) {
|
||||
extents.add(((TXT) d).getExtents());
|
||||
}
|
||||
}
|
||||
if (!prover.checkDnsTxt(extents)) {
|
||||
sendProofError(prover.getLog(), null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
byte[] messageBytes = prover.getPgpMessage().getBytes();
|
||||
if (prover.rawMessageCheckRequired()) {
|
||||
InputStream messageByteStream = PGPUtil.getDecoderStream(new ByteArrayInputStream(messageBytes));
|
||||
if (!prover.checkRawMessageBytes(messageByteStream)) {
|
||||
sendProofError(prover.getLog(), null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// kind of awkward, but this whole class wants to pull bytes out of “data”
|
||||
data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_BYTES);
|
||||
data.putByteArray(KeychainIntentService.DECRYPT_CIPHERTEXT_BYTES, messageBytes);
|
||||
|
||||
InputData inputData = createDecryptInputData(data);
|
||||
OutputStream outStream = createCryptOutputStream(data);
|
||||
|
||||
PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(
|
||||
this, new ProviderHelper(this), this,
|
||||
inputData, outStream
|
||||
);
|
||||
builder.setSignedLiteralData(true).setRequiredSignerFingerprint(requiredFingerprint);
|
||||
|
||||
DecryptVerifyResult decryptVerifyResult = builder.build().execute();
|
||||
outStream.close();
|
||||
|
||||
if (!decryptVerifyResult.success()) {
|
||||
OperationLog log = decryptVerifyResult.getLog();
|
||||
OperationResult.LogEntryParcel lastEntry = null;
|
||||
for (OperationResult.LogEntryParcel entry : log) {
|
||||
lastEntry = entry;
|
||||
}
|
||||
sendProofError(getString(lastEntry.mType.getMsgId()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!prover.validate(outStream.toString())) {
|
||||
sendProofError(getString(R.string.keybase_message_payload_mismatch));
|
||||
return;
|
||||
}
|
||||
|
||||
Bundle resultData = new Bundle();
|
||||
resultData.putString(KeychainIntentServiceHandler.DATA_MESSAGE, "OK");
|
||||
|
||||
// these help the handler construct a useful human-readable message
|
||||
resultData.putString(KeychainIntentServiceHandler.KEYBASE_PROOF_URL, prover.getProofUrl());
|
||||
resultData.putString(KeychainIntentServiceHandler.KEYBASE_PRESENCE_URL, prover.getPresenceUrl());
|
||||
resultData.putString(KeychainIntentServiceHandler.KEYBASE_PRESENCE_LABEL, prover.getPresenceLabel());
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ACTION_DECRYPT_VERIFY: {
|
||||
|
||||
try {
|
||||
/* Input */
|
||||
String passphrase = data.getString(DECRYPT_PASSPHRASE);
|
||||
byte[] nfcDecryptedSessionKey = data.getByteArray(DECRYPT_NFC_DECRYPTED_SESSION_KEY);
|
||||
String passphrase = data.getString(DECRYPT_PASSPHRASE);
|
||||
byte[] nfcDecryptedSessionKey = data.getByteArray(DECRYPT_NFC_DECRYPTED_SESSION_KEY);
|
||||
|
||||
InputData inputData = createDecryptInputData(data);
|
||||
OutputStream outStream = createCryptOutputStream(data);
|
||||
InputData inputData = createDecryptInputData(data);
|
||||
OutputStream outStream = createCryptOutputStream(data);
|
||||
|
||||
/* Operation */
|
||||
|
||||
Bundle resultData = new Bundle();
|
||||
Bundle resultData = new Bundle();
|
||||
|
||||
// verifyText and decrypt returning additional resultData values for the
|
||||
// verification of signatures
|
||||
PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(
|
||||
this, new ProviderHelper(this), this,
|
||||
inputData, outStream
|
||||
);
|
||||
builder.setAllowSymmetricDecryption(true)
|
||||
.setPassphrase(passphrase)
|
||||
.setNfcState(nfcDecryptedSessionKey);
|
||||
// verifyText and decrypt returning additional resultData values for the
|
||||
// verification of signatures
|
||||
PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(
|
||||
this, new ProviderHelper(this), this,
|
||||
inputData, outStream
|
||||
);
|
||||
builder.setAllowSymmetricDecryption(true)
|
||||
.setPassphrase(passphrase)
|
||||
.setNfcState(nfcDecryptedSessionKey);
|
||||
|
||||
DecryptVerifyResult decryptVerifyResult = builder.build().execute();
|
||||
DecryptVerifyResult decryptVerifyResult = builder.build().execute();
|
||||
|
||||
outStream.close();
|
||||
outStream.close();
|
||||
|
||||
resultData.putParcelable(DecryptVerifyResult.EXTRA_RESULT, decryptVerifyResult);
|
||||
resultData.putParcelable(DecryptVerifyResult.EXTRA_RESULT, decryptVerifyResult);
|
||||
|
||||
/* Output */
|
||||
|
||||
finalizeDecryptOutputStream(data, resultData, outStream);
|
||||
finalizeDecryptOutputStream(data, resultData, outStream);
|
||||
|
||||
Log.logDebugBundle(resultData, "resultData");
|
||||
Log.logDebugBundle(resultData, "resultData");
|
||||
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ACTION_DELETE: {
|
||||
|
||||
} else if (ACTION_DELETE.equals(action)) {
|
||||
// Input
|
||||
long[] masterKeyIds = data.getLongArray(DELETE_KEY_LIST);
|
||||
boolean isSecret = data.getBoolean(DELETE_IS_SECRET);
|
||||
|
||||
// Input
|
||||
long[] masterKeyIds = data.getLongArray(DELETE_KEY_LIST);
|
||||
boolean isSecret = data.getBoolean(DELETE_IS_SECRET);
|
||||
// Operation
|
||||
DeleteOperation op = new DeleteOperation(this, new ProviderHelper(this), this);
|
||||
DeleteResult result = op.execute(masterKeyIds, isSecret);
|
||||
|
||||
// Operation
|
||||
DeleteOperation op = new DeleteOperation(this, new ProviderHelper(this), this);
|
||||
DeleteResult result = op.execute(masterKeyIds, isSecret);
|
||||
// Result
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
|
||||
|
||||
// Result
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
|
||||
|
||||
} else if (ACTION_EDIT_KEYRING.equals(action)) {
|
||||
|
||||
// Input
|
||||
SaveKeyringParcel saveParcel = data.getParcelable(EDIT_KEYRING_PARCEL);
|
||||
String passphrase = data.getString(EDIT_KEYRING_PASSPHRASE);
|
||||
|
||||
// Operation
|
||||
EditKeyOperation op = new EditKeyOperation(this, providerHelper, this, mActionCanceled);
|
||||
EditKeyResult result = op.execute(saveParcel, passphrase);
|
||||
|
||||
// Result
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
|
||||
|
||||
} else if (ACTION_EXPORT_KEYRING.equals(action)) {
|
||||
|
||||
// Input
|
||||
boolean exportSecret = data.getBoolean(EXPORT_SECRET, false);
|
||||
String outputFile = data.getString(EXPORT_FILENAME);
|
||||
Uri outputUri = data.getParcelable(EXPORT_URI);
|
||||
|
||||
boolean exportAll = data.getBoolean(EXPORT_ALL);
|
||||
long[] masterKeyIds = exportAll ? null : data.getLongArray(EXPORT_KEY_RING_MASTER_KEY_ID);
|
||||
|
||||
// Operation
|
||||
ImportExportOperation importExportOperation = new ImportExportOperation(this, new ProviderHelper(this), this);
|
||||
ExportResult result;
|
||||
if (outputFile != null) {
|
||||
result = importExportOperation.exportToFile(masterKeyIds, exportSecret, outputFile);
|
||||
} else {
|
||||
result = importExportOperation.exportToUri(masterKeyIds, exportSecret, outputUri);
|
||||
break;
|
||||
}
|
||||
case ACTION_EDIT_KEYRING: {
|
||||
|
||||
// Result
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
|
||||
// Input
|
||||
SaveKeyringParcel saveParcel = data.getParcelable(EDIT_KEYRING_PARCEL);
|
||||
String passphrase = data.getString(EDIT_KEYRING_PASSPHRASE);
|
||||
|
||||
} else if (ACTION_IMPORT_KEYRING.equals(action)) {
|
||||
// Operation
|
||||
EditKeyOperation op = new EditKeyOperation(this, providerHelper, this, mActionCanceled);
|
||||
EditKeyResult result = op.execute(saveParcel, passphrase);
|
||||
|
||||
try {
|
||||
// Result
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
|
||||
|
||||
break;
|
||||
}
|
||||
case ACTION_PROMOTE_KEYRING: {
|
||||
|
||||
// Input
|
||||
long keyRingId = data.getInt(EXPORT_KEY_RING_MASTER_KEY_ID);
|
||||
|
||||
// Operation
|
||||
PromoteKeyOperation op = new PromoteKeyOperation(this, providerHelper, this, mActionCanceled);
|
||||
PromoteKeyResult result = op.execute(keyRingId);
|
||||
|
||||
// Result
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
|
||||
|
||||
break;
|
||||
}
|
||||
case ACTION_EXPORT_KEYRING: {
|
||||
|
||||
// Input
|
||||
boolean exportSecret = data.getBoolean(EXPORT_SECRET, false);
|
||||
String outputFile = data.getString(EXPORT_FILENAME);
|
||||
Uri outputUri = data.getParcelable(EXPORT_URI);
|
||||
|
||||
boolean exportAll = data.getBoolean(EXPORT_ALL);
|
||||
long[] masterKeyIds = exportAll ? null : data.getLongArray(EXPORT_KEY_RING_MASTER_KEY_ID);
|
||||
|
||||
// Operation
|
||||
ImportExportOperation importExportOperation = new ImportExportOperation(this, new ProviderHelper(this), this);
|
||||
ExportResult result;
|
||||
if (outputFile != null) {
|
||||
result = importExportOperation.exportToFile(masterKeyIds, exportSecret, outputFile);
|
||||
} else {
|
||||
result = importExportOperation.exportToUri(masterKeyIds, exportSecret, outputUri);
|
||||
}
|
||||
|
||||
// Result
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
|
||||
|
||||
break;
|
||||
}
|
||||
case ACTION_IMPORT_KEYRING: {
|
||||
|
||||
// Input
|
||||
String keyServer = data.getString(IMPORT_KEY_SERVER);
|
||||
Iterator<ParcelableKeyRing> entries;
|
||||
int numEntries;
|
||||
if (data.containsKey(IMPORT_KEY_LIST)) {
|
||||
// get entries from intent
|
||||
ArrayList<ParcelableKeyRing> list = data.getParcelableArrayList(IMPORT_KEY_LIST);
|
||||
entries = list.iterator();
|
||||
numEntries = list.size();
|
||||
} else {
|
||||
// get entries from cached file
|
||||
ParcelableFileCache<ParcelableKeyRing> cache =
|
||||
new ParcelableFileCache<ParcelableKeyRing>(this, "key_import.pcl");
|
||||
IteratorWithSize<ParcelableKeyRing> it = cache.readCache();
|
||||
entries = it;
|
||||
numEntries = it.getSize();
|
||||
}
|
||||
ArrayList<ParcelableKeyRing> list = data.getParcelableArrayList(IMPORT_KEY_LIST);
|
||||
ParcelableFileCache<ParcelableKeyRing> cache =
|
||||
new ParcelableFileCache<>(this, "key_import.pcl");
|
||||
|
||||
// Operation
|
||||
ImportExportOperation importExportOperation = new ImportExportOperation(
|
||||
this, providerHelper, this, mActionCanceled);
|
||||
ImportKeyResult result = importExportOperation.importKeyRings(entries, numEntries, keyServer);
|
||||
|
||||
// Special: consolidate on secret key import (cannot be cancelled!)
|
||||
if (result.mSecret > 0) {
|
||||
// TODO move this into the import operation
|
||||
providerHelper.consolidateDatabaseStep1(this);
|
||||
}
|
||||
|
||||
// Special: make sure new data is synced into contacts
|
||||
ContactSyncAdapterService.requestSync();
|
||||
// Either list or cache must be null, no guarantees otherwise.
|
||||
ImportKeyResult result = list != null
|
||||
? importExportOperation.importKeyRings(list, keyServer)
|
||||
: importExportOperation.importKeyRings(cache, keyServer);
|
||||
|
||||
// Result
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
case ACTION_SIGN_ENCRYPT: {
|
||||
|
||||
} else if (ACTION_SIGN_ENCRYPT.equals(action)) {
|
||||
// Input
|
||||
SignEncryptParcel inputParcel = data.getParcelable(SIGN_ENCRYPT_PARCEL);
|
||||
|
||||
try {
|
||||
/* Input */
|
||||
int source = data.get(SOURCE) != null ? data.getInt(SOURCE) : data.getInt(TARGET);
|
||||
Bundle resultData = new Bundle();
|
||||
// Operation
|
||||
SignEncryptOperation op = new SignEncryptOperation(
|
||||
this, new ProviderHelper(this), this, mActionCanceled);
|
||||
SignEncryptResult result = op.execute(inputParcel);
|
||||
|
||||
long sigMasterKeyId = data.getLong(ENCRYPT_SIGNATURE_MASTER_ID);
|
||||
String sigKeyPassphrase = data.getString(ENCRYPT_SIGNATURE_KEY_PASSPHRASE);
|
||||
// Result
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, result);
|
||||
|
||||
byte[] nfcHash = data.getByteArray(ENCRYPT_SIGNATURE_NFC_HASH);
|
||||
Date nfcTimestamp = (Date) data.getSerializable(ENCRYPT_SIGNATURE_NFC_TIMESTAMP);
|
||||
break;
|
||||
}
|
||||
case ACTION_UPLOAD_KEYRING: {
|
||||
try {
|
||||
|
||||
String symmetricPassphrase = data.getString(ENCRYPT_SYMMETRIC_PASSPHRASE);
|
||||
|
||||
boolean useAsciiArmor = data.getBoolean(ENCRYPT_USE_ASCII_ARMOR);
|
||||
long encryptionKeyIds[] = data.getLongArray(ENCRYPT_ENCRYPTION_KEYS_IDS);
|
||||
int compressionId = data.getInt(ENCRYPT_COMPRESSION_ID);
|
||||
int urisCount = data.containsKey(ENCRYPT_INPUT_URIS) ? data.getParcelableArrayList(ENCRYPT_INPUT_URIS).size() : 1;
|
||||
for (int i = 0; i < urisCount; i++) {
|
||||
data.putInt(SELECTED_URI, i);
|
||||
InputData inputData = createEncryptInputData(data);
|
||||
OutputStream outStream = createCryptOutputStream(data);
|
||||
String originalFilename = getOriginalFilename(data);
|
||||
/* Input */
|
||||
String keyServer = data.getString(UPLOAD_KEY_SERVER);
|
||||
// and dataUri!
|
||||
|
||||
/* Operation */
|
||||
PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder(
|
||||
this, new ProviderHelper(this), this, inputData, outStream
|
||||
);
|
||||
builder.setEnableAsciiArmorOutput(useAsciiArmor)
|
||||
.setVersionHeader(PgpHelper.getVersionForHeader(this))
|
||||
.setCompressionId(compressionId)
|
||||
.setSymmetricEncryptionAlgorithm(
|
||||
Preferences.getPreferences(this).getDefaultEncryptionAlgorithm())
|
||||
.setEncryptionMasterKeyIds(encryptionKeyIds)
|
||||
.setSymmetricPassphrase(symmetricPassphrase)
|
||||
.setOriginalFilename(originalFilename);
|
||||
HkpKeyserver server = new HkpKeyserver(keyServer);
|
||||
|
||||
CanonicalizedPublicKeyRing keyring = providerHelper.getCanonicalizedPublicKeyRing(dataUri);
|
||||
ImportExportOperation importExportOperation = new ImportExportOperation(this, new ProviderHelper(this), this);
|
||||
|
||||
try {
|
||||
|
||||
// Find the appropriate subkey to sign with
|
||||
CachedPublicKeyRing signingRing =
|
||||
new ProviderHelper(this).getCachedPublicKeyRing(sigMasterKeyId);
|
||||
long sigSubKeyId = signingRing.getSecretSignId();
|
||||
|
||||
// Set signature settings
|
||||
builder.setSignatureMasterKeyId(sigMasterKeyId)
|
||||
.setSignatureSubKeyId(sigSubKeyId)
|
||||
.setSignaturePassphrase(sigKeyPassphrase)
|
||||
.setSignatureHashAlgorithm(
|
||||
Preferences.getPreferences(this).getDefaultHashAlgorithm())
|
||||
.setAdditionalEncryptId(sigMasterKeyId);
|
||||
if (nfcHash != null && nfcTimestamp != null) {
|
||||
builder.setNfcState(nfcHash, nfcTimestamp);
|
||||
}
|
||||
|
||||
} catch (PgpKeyNotFoundException e) {
|
||||
// encrypt-only
|
||||
// TODO Just silently drop the requested signature? Shouldn't we throw here?
|
||||
importExportOperation.uploadKeyRingToServer(server, keyring);
|
||||
} catch (Keyserver.AddKeyException e) {
|
||||
throw new PgpGeneralException("Unable to export key to selected server");
|
||||
}
|
||||
|
||||
// this assumes that the bytes are cleartext (valid for current implementation!)
|
||||
if (source == IO_BYTES) {
|
||||
builder.setCleartextInput(true);
|
||||
}
|
||||
|
||||
SignEncryptResult result = builder.build().execute();
|
||||
resultData.putParcelable(SignEncryptResult.EXTRA_RESULT, result);
|
||||
|
||||
outStream.close();
|
||||
|
||||
/* Output */
|
||||
|
||||
finalizeEncryptOutputStream(data, resultData, outStream);
|
||||
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY);
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
|
||||
Log.logDebugBundle(resultData, "resultData");
|
||||
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
}
|
||||
|
||||
} else if (ACTION_UPLOAD_KEYRING.equals(action)) {
|
||||
|
||||
try {
|
||||
|
||||
/* Input */
|
||||
String keyServer = data.getString(UPLOAD_KEY_SERVER);
|
||||
// and dataUri!
|
||||
|
||||
/* Operation */
|
||||
HkpKeyserver server = new HkpKeyserver(keyServer);
|
||||
|
||||
CanonicalizedPublicKeyRing keyring = providerHelper.getCanonicalizedPublicKeyRing(dataUri);
|
||||
ImportExportOperation importExportOperation = new ImportExportOperation(this, new ProviderHelper(this), this);
|
||||
|
||||
try {
|
||||
importExportOperation.uploadKeyRingToServer(server, keyring);
|
||||
} catch (Keyserver.AddKeyException e) {
|
||||
throw new PgpGeneralException("Unable to export key to selected server");
|
||||
}
|
||||
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY);
|
||||
} catch (Exception e) {
|
||||
sendErrorToHandler(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendProofError(List<String> log, String label) {
|
||||
String msg = null;
|
||||
label = (label == null) ? "" : label + ": ";
|
||||
for (String m : log) {
|
||||
Log.e(Constants.TAG, label + m);
|
||||
msg = m;
|
||||
}
|
||||
sendProofError(label + msg);
|
||||
}
|
||||
|
||||
private void sendProofError(String msg) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(KeychainIntentServiceHandler.DATA_ERROR, msg);
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, bundle);
|
||||
}
|
||||
|
||||
private void sendErrorToHandler(Exception e) {
|
||||
@@ -532,7 +603,6 @@ public class KeychainIntentService extends IntentService implements Progressable
|
||||
} else {
|
||||
message = e.getMessage();
|
||||
}
|
||||
|
||||
Log.d(Constants.TAG, "KeychainIntentService Exception: ", e);
|
||||
|
||||
Bundle data = new Bundle();
|
||||
@@ -609,10 +679,6 @@ public class KeychainIntentService extends IntentService implements Progressable
|
||||
return createCryptInputData(data, DECRYPT_CIPHERTEXT_BYTES);
|
||||
}
|
||||
|
||||
private InputData createEncryptInputData(Bundle data) throws IOException, PgpGeneralException {
|
||||
return createCryptInputData(data, ENCRYPT_MESSAGE_BYTES);
|
||||
}
|
||||
|
||||
private InputData createCryptInputData(Bundle data, String bytesName) throws PgpGeneralException, IOException {
|
||||
int source = data.get(SOURCE) != null ? data.getInt(SOURCE) : data.getInt(TARGET);
|
||||
switch (source) {
|
||||
@@ -626,33 +692,6 @@ public class KeychainIntentService extends IntentService implements Progressable
|
||||
// InputStream
|
||||
return new InputData(getContentResolver().openInputStream(providerUri), FileHelper.getFileSize(this, providerUri, 0));
|
||||
|
||||
case IO_URIS:
|
||||
providerUri = data.<Uri>getParcelableArrayList(ENCRYPT_INPUT_URIS).get(data.getInt(SELECTED_URI));
|
||||
|
||||
// InputStream
|
||||
return new InputData(getContentResolver().openInputStream(providerUri), FileHelper.getFileSize(this, providerUri, 0));
|
||||
|
||||
default:
|
||||
throw new PgpGeneralException("No target choosen!");
|
||||
}
|
||||
}
|
||||
|
||||
private String getOriginalFilename(Bundle data) throws PgpGeneralException, FileNotFoundException {
|
||||
int target = data.getInt(TARGET);
|
||||
switch (target) {
|
||||
case IO_BYTES:
|
||||
return "";
|
||||
|
||||
case IO_URI:
|
||||
Uri providerUri = data.getParcelable(ENCRYPT_DECRYPT_INPUT_URI);
|
||||
|
||||
return FileHelper.getFilename(this, providerUri);
|
||||
|
||||
case IO_URIS:
|
||||
providerUri = data.<Uri>getParcelableArrayList(ENCRYPT_INPUT_URIS).get(data.getInt(SELECTED_URI));
|
||||
|
||||
return FileHelper.getFilename(this, providerUri);
|
||||
|
||||
default:
|
||||
throw new PgpGeneralException("No target choosen!");
|
||||
}
|
||||
@@ -669,20 +708,11 @@ public class KeychainIntentService extends IntentService implements Progressable
|
||||
|
||||
return getContentResolver().openOutputStream(providerUri);
|
||||
|
||||
case IO_URIS:
|
||||
providerUri = data.<Uri>getParcelableArrayList(ENCRYPT_OUTPUT_URIS).get(data.getInt(SELECTED_URI));
|
||||
|
||||
return getContentResolver().openOutputStream(providerUri);
|
||||
|
||||
default:
|
||||
throw new PgpGeneralException("No target choosen!");
|
||||
}
|
||||
}
|
||||
|
||||
private void finalizeEncryptOutputStream(Bundle data, Bundle resultData, OutputStream outStream) {
|
||||
finalizeCryptOutputStream(data, resultData, outStream, RESULT_BYTES);
|
||||
}
|
||||
|
||||
private void finalizeDecryptOutputStream(Bundle data, Bundle resultData, OutputStream outStream) {
|
||||
finalizeCryptOutputStream(data, resultData, outStream, RESULT_DECRYPTED_BYTES);
|
||||
}
|
||||
@@ -695,7 +725,6 @@ public class KeychainIntentService extends IntentService implements Progressable
|
||||
resultData.putByteArray(bytesName, output);
|
||||
break;
|
||||
case IO_URI:
|
||||
case IO_URIS:
|
||||
// nothing, output was written, just send okay and verification bundle
|
||||
|
||||
break;
|
||||
|
||||
+13
-2
@@ -27,8 +27,8 @@ import android.support.v4.app.FragmentManager;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
public class KeychainIntentServiceHandler extends Handler {
|
||||
|
||||
@@ -45,6 +45,11 @@ public class KeychainIntentServiceHandler extends Handler {
|
||||
public static final String DATA_MESSAGE = "message";
|
||||
public static final String DATA_MESSAGE_ID = "message_id";
|
||||
|
||||
// keybase proof specific
|
||||
public static final String KEYBASE_PROOF_URL = "keybase_proof_url";
|
||||
public static final String KEYBASE_PRESENCE_URL = "keybase_presence_url";
|
||||
public static final String KEYBASE_PRESENCE_LABEL = "keybase_presence_label";
|
||||
|
||||
Activity mActivity;
|
||||
ProgressDialogFragment mProgressDialogFragment;
|
||||
|
||||
@@ -73,6 +78,10 @@ public class KeychainIntentServiceHandler extends Handler {
|
||||
}
|
||||
|
||||
public void showProgressDialog(FragmentActivity activity) {
|
||||
if (mProgressDialogFragment == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: This is a hack!, see
|
||||
// http://stackoverflow.com/questions/10114324/show-dialogfragment-from-onactivityresult
|
||||
final FragmentManager manager = activity.getSupportFragmentManager();
|
||||
@@ -89,7 +98,8 @@ public class KeychainIntentServiceHandler extends Handler {
|
||||
Bundle data = message.getData();
|
||||
|
||||
if (mProgressDialogFragment == null) {
|
||||
Log.e(Constants.TAG, "Progress has not been updated because mProgressDialogFragment was null!");
|
||||
// Log.e(Constants.TAG,
|
||||
// "Progress has not been updated because mProgressDialogFragment was null!");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -131,6 +141,7 @@ public class KeychainIntentServiceHandler extends Handler {
|
||||
|
||||
case MESSAGE_PREVENT_CANCEL:
|
||||
mProgressDialogFragment.setPreventCancel(true);
|
||||
break;
|
||||
|
||||
default:
|
||||
Log.e(Constants.TAG, "unknown handler message!");
|
||||
|
||||
+2
-3
@@ -39,12 +39,11 @@ import android.support.v4.util.LongSparseArray;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
|
||||
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@@ -103,7 +102,7 @@ public class PassphraseCacheService extends Service {
|
||||
|
||||
private BroadcastReceiver mIntentReceiver;
|
||||
|
||||
private LongSparseArray<CachedPassphrase> mPassphraseCache = new LongSparseArray<CachedPassphrase>();
|
||||
private LongSparseArray<CachedPassphrase> mPassphraseCache = new LongSparseArray<>();
|
||||
|
||||
Context mContext;
|
||||
|
||||
|
||||
+53
-14
@@ -59,7 +59,6 @@ public class SaveKeyringParcel implements Parcelable {
|
||||
|
||||
public ArrayList<String> mRevokeUserIds;
|
||||
public ArrayList<Long> mRevokeSubKeys;
|
||||
public ArrayList<Long> mStripSubKeys;
|
||||
|
||||
public SaveKeyringParcel() {
|
||||
reset();
|
||||
@@ -73,14 +72,30 @@ public class SaveKeyringParcel implements Parcelable {
|
||||
|
||||
public void reset() {
|
||||
mNewUnlock = null;
|
||||
mAddUserIds = new ArrayList<String>();
|
||||
mAddUserAttribute = new ArrayList<WrappedUserAttribute>();
|
||||
mAddSubKeys = new ArrayList<SubkeyAdd>();
|
||||
mAddUserIds = new ArrayList<>();
|
||||
mAddUserAttribute = new ArrayList<>();
|
||||
mAddSubKeys = new ArrayList<>();
|
||||
mChangePrimaryUserId = null;
|
||||
mChangeSubKeys = new ArrayList<SubkeyChange>();
|
||||
mRevokeUserIds = new ArrayList<String>();
|
||||
mRevokeSubKeys = new ArrayList<Long>();
|
||||
mStripSubKeys = new ArrayList<Long>();
|
||||
mChangeSubKeys = new ArrayList<>();
|
||||
mRevokeUserIds = new ArrayList<>();
|
||||
mRevokeSubKeys = new ArrayList<>();
|
||||
}
|
||||
|
||||
/** Returns true iff this parcel does not contain any operations which require a passphrase. */
|
||||
public boolean isRestrictedOnly() {
|
||||
if (mNewUnlock != null || !mAddUserIds.isEmpty() || !mAddUserAttribute.isEmpty()
|
||||
|| !mAddSubKeys.isEmpty() || mChangePrimaryUserId != null || !mRevokeSubKeys .isEmpty()
|
||||
|| !mRevokeSubKeys.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (SubkeyChange change : mChangeSubKeys) {
|
||||
if (change.mRecertify || change.mFlags != null || change.mExpiry != null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// performance gain for using Parcelable here would probably be negligible,
|
||||
@@ -113,26 +128,53 @@ public class SaveKeyringParcel implements Parcelable {
|
||||
}
|
||||
|
||||
public static class SubkeyChange implements Serializable {
|
||||
public long mKeyId;
|
||||
public final long mKeyId;
|
||||
public Integer mFlags;
|
||||
// this is a long unix timestamp, in seconds (NOT MILLISECONDS!)
|
||||
public Long mExpiry;
|
||||
// if this flag is true, the key will be recertified even if all above
|
||||
// values are no-ops
|
||||
public boolean mRecertify;
|
||||
// if this flag is true, the subkey should be changed to a stripped key
|
||||
public boolean mDummyStrip;
|
||||
// if this is non-null, the subkey will be changed to a divert-to-card
|
||||
// key for the given serial number
|
||||
public byte[] mDummyDivert;
|
||||
|
||||
public SubkeyChange(long keyId) {
|
||||
mKeyId = keyId;
|
||||
}
|
||||
|
||||
public SubkeyChange(long keyId, boolean recertify) {
|
||||
mKeyId = keyId;
|
||||
mRecertify = recertify;
|
||||
}
|
||||
|
||||
public SubkeyChange(long keyId, Integer flags, Long expiry) {
|
||||
mKeyId = keyId;
|
||||
mFlags = flags;
|
||||
mExpiry = expiry;
|
||||
}
|
||||
|
||||
public SubkeyChange(long keyId, boolean dummyStrip, byte[] dummyDivert) {
|
||||
this(keyId, null, null);
|
||||
|
||||
// these flags are mutually exclusive!
|
||||
if (dummyStrip && dummyDivert != null) {
|
||||
throw new AssertionError(
|
||||
"cannot set strip and divert flags at the same time - this is a bug!");
|
||||
}
|
||||
mDummyStrip = dummyStrip;
|
||||
mDummyDivert = dummyDivert;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String out = "mKeyId: " + mKeyId + ", ";
|
||||
out += "mFlags: " + mFlags + ", ";
|
||||
out += "mExpiry: " + mExpiry;
|
||||
out += "mExpiry: " + mExpiry + ", ";
|
||||
out += "mDummyStrip: " + mDummyStrip + ", ";
|
||||
out += "mDummyDivert: [" + (mDummyDivert == null ? 0 : mDummyDivert.length) + " bytes]";
|
||||
|
||||
return out;
|
||||
}
|
||||
@@ -174,7 +216,6 @@ public class SaveKeyringParcel implements Parcelable {
|
||||
|
||||
mRevokeUserIds = source.createStringArrayList();
|
||||
mRevokeSubKeys = (ArrayList<Long>) source.readSerializable();
|
||||
mStripSubKeys = (ArrayList<Long>) source.readSerializable();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -197,7 +238,6 @@ public class SaveKeyringParcel implements Parcelable {
|
||||
|
||||
destination.writeStringList(mRevokeUserIds);
|
||||
destination.writeSerializable(mRevokeSubKeys);
|
||||
destination.writeSerializable(mStripSubKeys);
|
||||
}
|
||||
|
||||
public static final Creator<SaveKeyringParcel> CREATOR = new Creator<SaveKeyringParcel>() {
|
||||
@@ -225,8 +265,7 @@ public class SaveKeyringParcel implements Parcelable {
|
||||
out += "mChangeSubKeys: " + mChangeSubKeys + "\n";
|
||||
out += "mChangePrimaryUserId: " + mChangePrimaryUserId + "\n";
|
||||
out += "mRevokeUserIds: " + mRevokeUserIds + "\n";
|
||||
out += "mRevokeSubKeys: " + mRevokeSubKeys + "\n";
|
||||
out += "mStripSubKeys: " + mStripSubKeys;
|
||||
out += "mRevokeSubKeys: " + mRevokeSubKeys;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* 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.os.Bundle;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
|
||||
/**
|
||||
* Setups Toolbar
|
||||
*/
|
||||
public abstract class BaseActivity extends ActionBarActivity {
|
||||
protected Toolbar mToolbar;
|
||||
protected View mStatusBar;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
initLayout();
|
||||
initToolbar();
|
||||
}
|
||||
|
||||
protected abstract void initLayout();
|
||||
|
||||
protected void initToolbar() {
|
||||
mToolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
if (mToolbar != null) {
|
||||
setSupportActionBar(mToolbar);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
mStatusBar = findViewById(R.id.status_bar);
|
||||
}
|
||||
|
||||
protected void setActionBarIcon(int iconRes) {
|
||||
mToolbar.setNavigationIcon(iconRes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate custom design to look like a full screen dialog, as specified in Material Design Guidelines
|
||||
* see http://www.google.com/design/spec/components/dialogs.html#dialogs-full-screen-dialogs
|
||||
*/
|
||||
protected void setFullScreenDialogDoneClose(int doneText, View.OnClickListener doneOnClickListener,
|
||||
View.OnClickListener cancelOnClickListener) {
|
||||
setActionBarIcon(R.drawable.ic_close_white_24dp);
|
||||
|
||||
// Inflate the custom action bar view
|
||||
final LayoutInflater inflater = (LayoutInflater) getSupportActionBar().getThemedContext()
|
||||
.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
|
||||
final View customActionBarView = inflater.inflate(R.layout.full_screen_dialog, null);
|
||||
|
||||
TextView firstTextView = ((TextView) customActionBarView.findViewById(R.id.full_screen_dialog_done_text));
|
||||
firstTextView.setText(doneText);
|
||||
customActionBarView.findViewById(R.id.full_screen_dialog_done).setOnClickListener(
|
||||
doneOnClickListener);
|
||||
|
||||
getSupportActionBar().setDisplayShowCustomEnabled(true);
|
||||
getSupportActionBar().setDisplayShowTitleEnabled(true);
|
||||
getSupportActionBar().setCustomView(customActionBarView, new ActionBar.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
Gravity.END));
|
||||
mToolbar.setNavigationOnClickListener(cancelOnClickListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close button only
|
||||
*/
|
||||
protected void setFullScreenDialogClose(View.OnClickListener cancelOnClickListener) {
|
||||
setActionBarIcon(R.drawable.ic_close_white_24dp);
|
||||
getSupportActionBar().setDisplayShowTitleEnabled(true);
|
||||
mToolbar.setNavigationOnClickListener(cancelOnClickListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate custom design with two buttons using drawables.
|
||||
* This does not conform to the Material Design Guidelines, but we deviate here as this is used
|
||||
* to indicate "Allow access"/"Disallow access" to the API, which must be clearly indicated
|
||||
*/
|
||||
protected void setFullScreenDialogTwoButtons(int firstText, int firstDrawableId, View.OnClickListener firstOnClickListener,
|
||||
int secondText, int secondDrawableId, View.OnClickListener secondOnClickListener) {
|
||||
|
||||
// Inflate the custom action bar view
|
||||
final LayoutInflater inflater = (LayoutInflater) getSupportActionBar().getThemedContext()
|
||||
.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
|
||||
final View customActionBarView = inflater.inflate(
|
||||
R.layout.full_screen_dialog_2, null);
|
||||
|
||||
TextView firstTextView = ((TextView) customActionBarView.findViewById(R.id.actionbar_done_text));
|
||||
firstTextView.setText(firstText);
|
||||
firstTextView.setCompoundDrawablesWithIntrinsicBounds(firstDrawableId, 0, 0, 0);
|
||||
customActionBarView.findViewById(R.id.actionbar_done).setOnClickListener(
|
||||
firstOnClickListener);
|
||||
TextView secondTextView = ((TextView) customActionBarView.findViewById(R.id.actionbar_cancel_text));
|
||||
secondTextView.setText(secondText);
|
||||
secondTextView.setCompoundDrawablesWithIntrinsicBounds(secondDrawableId, 0, 0, 0);
|
||||
customActionBarView.findViewById(R.id.actionbar_cancel).setOnClickListener(
|
||||
secondOnClickListener);
|
||||
|
||||
// Show the custom action bar view and hide the normal Home icon and title.
|
||||
getSupportActionBar().setDisplayShowTitleEnabled(false);
|
||||
getSupportActionBar().setDisplayShowHomeEnabled(false);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
|
||||
getSupportActionBar().setDisplayShowCustomEnabled(true);
|
||||
getSupportActionBar().setCustomView(customActionBarView, new ActionBar.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
}
|
||||
|
||||
}
|
||||
+21
-34
@@ -1,6 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2013 Bahtiar 'kalkin' Gadimov
|
||||
* 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
|
||||
@@ -20,55 +19,43 @@ package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.View;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.ui.util.ActionBarHelper;
|
||||
import org.sufficientlysecure.keychain.util.ExportHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
public class ViewKeyAdvancedActivity extends ActionBarActivity {
|
||||
public class CertifyFingerprintActivity extends BaseActivity {
|
||||
|
||||
ExportHelper mExportHelper;
|
||||
ProviderHelper mProviderHelper;
|
||||
protected Uri mDataUri;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mExportHelper = new ExportHelper(this);
|
||||
mProviderHelper = new ProviderHelper(this);
|
||||
|
||||
// Inflate a "Done" custom action bar
|
||||
ActionBarHelper.setOneButtonView(getSupportActionBar(),
|
||||
R.string.btn_okay, R.drawable.ic_action_done,
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// "Done"
|
||||
finish();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
setContentView(R.layout.view_key_advanced_activity);
|
||||
|
||||
Uri dataUri = getIntent().getData();
|
||||
if (dataUri == null) {
|
||||
mDataUri = getIntent().getData();
|
||||
if (mDataUri == null) {
|
||||
Log.e(Constants.TAG, "Data missing. Should be uri of key!");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i(Constants.TAG, "mDataUri: " + dataUri.toString());
|
||||
setFullScreenDialogClose(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
startFragment(savedInstanceState, dataUri);
|
||||
Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString());
|
||||
|
||||
startFragment(savedInstanceState, mDataUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initLayout() {
|
||||
setContentView(R.layout.certify_fingerprint_activity);
|
||||
}
|
||||
|
||||
private void startFragment(Bundle savedInstanceState, Uri dataUri) {
|
||||
// However, if we're being restored from a previous state,
|
||||
@@ -79,12 +66,12 @@ public class ViewKeyAdvancedActivity extends ActionBarActivity {
|
||||
}
|
||||
|
||||
// Create an instance of the fragment
|
||||
ViewKeyAdvancedFragment frag = ViewKeyAdvancedFragment.newInstance(dataUri);
|
||||
CertifyFingerprintFragment frag = CertifyFingerprintFragment.newInstance(dataUri);
|
||||
|
||||
// Add the fragment to the 'fragment_container' FrameLayout
|
||||
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.view_key_advanced_fragment, frag)
|
||||
.replace(R.id.certify_fingerprint_fragment, frag)
|
||||
.commitAllowingStateLoss();
|
||||
// do it immediately!
|
||||
getSupportFragmentManager().executePendingTransactions();
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* 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.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
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.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
|
||||
public class CertifyFingerprintFragment extends LoaderFragment implements
|
||||
LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
static final int REQUEST_CERTIFY = 1;
|
||||
|
||||
public static final String ARG_DATA_URI = "uri";
|
||||
|
||||
private TextView mFingerprint;
|
||||
|
||||
private static final int LOADER_ID_UNIFIED = 0;
|
||||
|
||||
private Uri mDataUri;
|
||||
|
||||
private View mActionNo;
|
||||
private View mActionYes;
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static CertifyFingerprintFragment newInstance(Uri dataUri) {
|
||||
CertifyFingerprintFragment frag = new CertifyFingerprintFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(ARG_DATA_URI, dataUri);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
|
||||
View root = super.onCreateView(inflater, superContainer, savedInstanceState);
|
||||
View view = inflater.inflate(R.layout.certify_fingerprint_fragment, getContainer());
|
||||
|
||||
mActionNo = view.findViewById(R.id.certify_fingerprint_button_no);
|
||||
mActionYes = view.findViewById(R.id.certify_fingerprint_button_yes);
|
||||
|
||||
mFingerprint = (TextView) view.findViewById(R.id.certify_fingerprint_fingerprint);
|
||||
|
||||
mActionNo.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
getActivity().finish();
|
||||
}
|
||||
});
|
||||
mActionYes.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
certify(mDataUri);
|
||||
}
|
||||
});
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
Uri dataUri = getArguments().getParcelable(ARG_DATA_URI);
|
||||
if (dataUri == null) {
|
||||
Log.e(Constants.TAG, "Data missing. Should be Uri of key!");
|
||||
getActivity().finish();
|
||||
return;
|
||||
}
|
||||
|
||||
loadData(dataUri);
|
||||
}
|
||||
|
||||
private void loadData(Uri dataUri) {
|
||||
mDataUri = dataUri;
|
||||
|
||||
Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString());
|
||||
|
||||
// Prepare the loaders. Either re-connect with an existing ones,
|
||||
// or start new ones.
|
||||
getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this);
|
||||
}
|
||||
|
||||
static final String[] UNIFIED_PROJECTION = new String[]{
|
||||
KeyRings._ID, KeyRings.FINGERPRINT,
|
||||
|
||||
};
|
||||
static final int INDEX_UNIFIED_FINGERPRINT = 1;
|
||||
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
setContentShown(false);
|
||||
switch (id) {
|
||||
case LOADER_ID_UNIFIED: {
|
||||
Uri baseUri = KeyRings.buildUnifiedKeyRingUri(mDataUri);
|
||||
return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null);
|
||||
}
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
/* TODO better error handling? May cause problems when a key is deleted,
|
||||
* because the notification triggers faster than the activity closes.
|
||||
*/
|
||||
// Avoid NullPointerExceptions...
|
||||
if (data.getCount() == 0) {
|
||||
return;
|
||||
}
|
||||
// Swap the new cursor in. (The framework will take care of closing the
|
||||
// old cursor once we return.)
|
||||
switch (loader.getId()) {
|
||||
case LOADER_ID_UNIFIED: {
|
||||
if (data.moveToFirst()) {
|
||||
|
||||
byte[] fingerprintBlob = data.getBlob(INDEX_UNIFIED_FINGERPRINT);
|
||||
String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fingerprintBlob);
|
||||
mFingerprint.setText(KeyFormattingUtils.colorizeFingerprint(fingerprint));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
setContentShown(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called when the last Cursor provided to onLoadFinished() above is about to be closed.
|
||||
* We need to make sure we are no longer using it.
|
||||
*/
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
}
|
||||
|
||||
private void certify(Uri dataUri) {
|
||||
long keyId = 0;
|
||||
try {
|
||||
keyId = new ProviderHelper(getActivity())
|
||||
.getCachedPublicKeyRing(dataUri)
|
||||
.extractOrGetMasterKeyId();
|
||||
} catch (PgpKeyNotFoundException e) {
|
||||
Log.e(Constants.TAG, "key not found!", e);
|
||||
}
|
||||
Intent certifyIntent = new Intent(getActivity(), CertifyKeyActivity.class);
|
||||
certifyIntent.putExtras(getActivity().getIntent());
|
||||
certifyIntent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, new long[]{keyId});
|
||||
startActivityForResult(certifyIntent, REQUEST_CERTIFY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
// always just pass this one through
|
||||
if (requestCode == REQUEST_CERTIFY) {
|
||||
getActivity().setResult(resultCode, data);
|
||||
getActivity().finish();
|
||||
return;
|
||||
}
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
}
|
||||
+2
-7
@@ -18,24 +18,19 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
|
||||
/**
|
||||
* Signs the specified public key with the specified secret master key
|
||||
*/
|
||||
public class CertifyKeyActivity extends ActionBarActivity {
|
||||
public class CertifyKeyActivity extends BaseActivity {
|
||||
|
||||
public static final String EXTRA_RESULT = "operation_result";
|
||||
public static final String EXTRA_KEY_IDS = "extra_key_ids";
|
||||
public static final String EXTRA_CERTIFY_KEY_ID = "certify_key_id";
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
protected void initLayout() {
|
||||
setContentView(R.layout.certify_key_activity);
|
||||
}
|
||||
|
||||
|
||||
+50
-34
@@ -31,6 +31,7 @@ import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
@@ -94,6 +95,7 @@ public class CertifyKeyFragment extends LoaderFragment
|
||||
private static final int INDEX_IS_REVOKED = 4;
|
||||
|
||||
private MultiUserIdsAdapter mUserIdsAdapter;
|
||||
private Messenger mPassthroughMessenger;
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
@@ -109,6 +111,9 @@ public class CertifyKeyFragment extends LoaderFragment
|
||||
return;
|
||||
}
|
||||
|
||||
mPassthroughMessenger = getActivity().getIntent().getParcelableExtra(
|
||||
KeychainIntentService.EXTRA_MESSENGER);
|
||||
|
||||
// preselect certify key id if given
|
||||
long certifyKeyId = getActivity().getIntent().getLongExtra(CertifyKeyActivity.EXTRA_CERTIFY_KEY_ID, Constants.key.none);
|
||||
if (certifyKeyId != Constants.key.none) {
|
||||
@@ -234,7 +239,7 @@ public class CertifyKeyFragment extends LoaderFragment
|
||||
|
||||
long lastMasterKeyId = 0;
|
||||
String lastName = "";
|
||||
ArrayList<String> uids = new ArrayList<String>();
|
||||
ArrayList<String> uids = new ArrayList<>();
|
||||
|
||||
boolean header = true;
|
||||
|
||||
@@ -332,7 +337,6 @@ public class CertifyKeyFragment extends LoaderFragment
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
@@ -362,51 +366,63 @@ public class CertifyKeyFragment extends LoaderFragment
|
||||
return;
|
||||
}
|
||||
|
||||
Bundle data = new Bundle();
|
||||
{
|
||||
// fill values for this action
|
||||
CertifyActionsParcel parcel = new CertifyActionsParcel(mSignMasterKeyId);
|
||||
parcel.mCertifyActions.addAll(certifyActions);
|
||||
|
||||
data.putParcelable(KeychainIntentService.CERTIFY_PARCEL, parcel);
|
||||
if (mUploadKeyCheckbox.isChecked()) {
|
||||
String keyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver();
|
||||
data.putString(KeychainIntentService.UPLOAD_KEY_SERVER, keyserver);
|
||||
}
|
||||
}
|
||||
|
||||
// Send all information needed to service to sign key in other thread
|
||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
||||
|
||||
intent.setAction(KeychainIntentService.ACTION_CERTIFY_KEYRING);
|
||||
|
||||
// fill values for this action
|
||||
CertifyActionsParcel parcel = new CertifyActionsParcel(mSignMasterKeyId);
|
||||
parcel.mCertifyActions.addAll(certifyActions);
|
||||
|
||||
Bundle data = new Bundle();
|
||||
data.putParcelable(KeychainIntentService.CERTIFY_PARCEL, parcel);
|
||||
if (mUploadKeyCheckbox.isChecked()) {
|
||||
String keyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver();
|
||||
data.putString(KeychainIntentService.UPLOAD_KEY_SERVER, keyserver);
|
||||
}
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after signing is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity(),
|
||||
getString(R.string.progress_certifying), ProgressDialog.STYLE_SPINNER, true) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
if (mPassthroughMessenger != null) {
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, mPassthroughMessenger);
|
||||
} else {
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
Bundle data = message.getData();
|
||||
CertifyResult result = data.getParcelable(CertifyResult.EXTRA_RESULT);
|
||||
// Message is received after signing is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity(),
|
||||
getString(R.string.progress_certifying), ProgressDialog.STYLE_SPINNER, true) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(CertifyResult.EXTRA_RESULT, result);
|
||||
getActivity().setResult(Activity.RESULT_OK, intent);
|
||||
getActivity().finish();
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
Bundle data = message.getData();
|
||||
CertifyResult result = data.getParcelable(CertifyResult.EXTRA_RESULT);
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(CertifyResult.EXTRA_RESULT, result);
|
||||
getActivity().setResult(Activity.RESULT_OK, intent);
|
||||
getActivity().finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
saveHandler.showProgressDialog(getActivity());
|
||||
// show progress dialog
|
||||
saveHandler.showProgressDialog(getActivity());
|
||||
}
|
||||
|
||||
// start service with intent
|
||||
getActivity().startService(intent);
|
||||
|
||||
if (mPassthroughMessenger != null) {
|
||||
getActivity().setResult(Activity.RESULT_OK);
|
||||
getActivity().finish();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -61,7 +61,7 @@ public class ConsolidateDialogActivity extends FragmentActivity {
|
||||
/* don't care about the results (for now?)
|
||||
|
||||
// get returned data bundle
|
||||
Bundle returnData = message.getData();
|
||||
Bundle returnData = message.getInputData();
|
||||
if (returnData == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
+6
-4
@@ -20,11 +20,10 @@ package org.sufficientlysecure.keychain.ui;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
|
||||
public class CreateKeyActivity extends ActionBarActivity {
|
||||
public class CreateKeyActivity extends BaseActivity {
|
||||
|
||||
public static final String EXTRA_NAME = "name";
|
||||
public static final String EXTRA_EMAIL = "email";
|
||||
@@ -37,8 +36,6 @@ public class CreateKeyActivity extends ActionBarActivity {
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.create_key_activity);
|
||||
|
||||
// pass extras into fragment
|
||||
CreateKeyInputFragment frag =
|
||||
CreateKeyInputFragment.newInstance(
|
||||
@@ -48,6 +45,11 @@ public class CreateKeyActivity extends ActionBarActivity {
|
||||
loadFragment(null, frag, FRAG_ACTION_START);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initLayout() {
|
||||
setContentView(R.layout.create_key_activity);
|
||||
}
|
||||
|
||||
public void loadFragment(Bundle savedInstanceState, Fragment fragment, int action) {
|
||||
// However, if we're being restored from a previous state,
|
||||
// then we don't need to do anything and should return or else
|
||||
|
||||
+3
-4
@@ -35,17 +35,16 @@ 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.service.SaveKeyringParcel.ChangeUnlockParcel;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
|
||||
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
public class CreateKeyFinalFragment extends Fragment {
|
||||
|
||||
|
||||
+2
-2
@@ -89,7 +89,7 @@ public class CreateKeyInputFragment extends Fragment {
|
||||
|
||||
mEmailEdit.setThreshold(1); // Start working from first character
|
||||
mEmailEdit.setAdapter(
|
||||
new ArrayAdapter<String>
|
||||
new ArrayAdapter<>
|
||||
(getActivity(), android.R.layout.simple_spinner_dropdown_item,
|
||||
ContactHelper.getPossibleUserEmails(getActivity())
|
||||
)
|
||||
@@ -124,7 +124,7 @@ public class CreateKeyInputFragment extends Fragment {
|
||||
|
||||
mNameEdit.setThreshold(1); // Start working from first character
|
||||
mNameEdit.setAdapter(
|
||||
new ArrayAdapter<String>
|
||||
new ArrayAdapter<>
|
||||
(getActivity(), android.R.layout.simple_spinner_dropdown_item,
|
||||
ContactHelper.getPossibleUserNames(getActivity())
|
||||
)
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
/*
|
||||
* 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.ui;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.ui.util.SubtleAttentionSeeker;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
public class DecryptActivity extends DrawerActivity {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.decrypt_activity);
|
||||
|
||||
activateDrawerNavigation(savedInstanceState);
|
||||
|
||||
View actionFile = findViewById(R.id.decrypt_files);
|
||||
View actionFromClipboard = findViewById(R.id.decrypt_from_clipboard);
|
||||
|
||||
actionFile.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent filesDecrypt = new Intent(DecryptActivity.this, DecryptFilesActivity.class);
|
||||
filesDecrypt.setAction(DecryptFilesActivity.ACTION_DECRYPT_DATA_OPEN);
|
||||
startActivity(filesDecrypt);
|
||||
}
|
||||
});
|
||||
|
||||
actionFromClipboard.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent clipboardDecrypt = new Intent(DecryptActivity.this, DecryptTextActivity.class);
|
||||
clipboardDecrypt.setAction(DecryptTextActivity.ACTION_DECRYPT_FROM_CLIPBOARD);
|
||||
startActivityForResult(clipboardDecrypt, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@TargetApi(VERSION_CODES.HONEYCOMB)
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
// This is an eye candy ice cream sandwich feature, nvm on versions below
|
||||
if (Build.VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
|
||||
// get text from clipboard
|
||||
final CharSequence clipboardText =
|
||||
ClipboardReflection.getClipboardText(DecryptActivity.this);
|
||||
|
||||
// if it's null, nothing to do here /o/
|
||||
if (clipboardText == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
new AsyncTask<String, Void, Boolean>() {
|
||||
@Override
|
||||
protected Boolean doInBackground(String... clipboardText) {
|
||||
|
||||
// see if it looks like a pgp thing
|
||||
Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(clipboardText[0]);
|
||||
boolean animate = matcher.matches();
|
||||
|
||||
// see if it looks like another pgp thing
|
||||
if (!animate) {
|
||||
matcher = PgpHelper.PGP_CLEARTEXT_SIGNATURE.matcher(clipboardText[0]);
|
||||
animate = matcher.matches();
|
||||
}
|
||||
return animate;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean animate) {
|
||||
super.onPostExecute(animate);
|
||||
|
||||
// if so, animate the clipboard icon just a bit~
|
||||
if (animate) {
|
||||
SubtleAttentionSeeker.tada(findViewById(R.id.clipboard_icon), 1.5f).start();
|
||||
}
|
||||
}
|
||||
}.execute(clipboardText.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
// if a result has been returned, display a notify
|
||||
if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) {
|
||||
OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT);
|
||||
result.createNotify(this).show();
|
||||
} else {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-5
@@ -20,14 +20,13 @@ package org.sufficientlysecure.keychain.ui;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.api.OpenKeychainIntents;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
public class DecryptFilesActivity extends ActionBarActivity {
|
||||
public class DecryptFilesActivity extends BaseActivity {
|
||||
|
||||
/* Intents */
|
||||
public static final String ACTION_DECRYPT_DATA = OpenKeychainIntents.DECRYPT_DATA;
|
||||
@@ -41,12 +40,15 @@ public class DecryptFilesActivity extends ActionBarActivity {
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.decrypt_files_activity);
|
||||
|
||||
// Handle intent actions
|
||||
handleActions(savedInstanceState, getIntent());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initLayout() {
|
||||
setContentView(R.layout.decrypt_files_activity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles all actions with this intent
|
||||
*
|
||||
@@ -86,7 +88,7 @@ public class DecryptFilesActivity extends ActionBarActivity {
|
||||
loadFragment(savedInstanceState, null, true);
|
||||
} else if (ACTION_DECRYPT_DATA.equals(action)) {
|
||||
Log.e(Constants.TAG,
|
||||
"Include an Uri with setData() in your Intent!");
|
||||
"Include an Uri with setInputData() in your Intent!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -35,13 +35,13 @@ import android.widget.TextView;
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.util.FileHelper;
|
||||
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.util.FileHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@@ -310,7 +310,7 @@ public class DecryptFilesFragment extends DecryptFragment {
|
||||
// A future open after decryption feature
|
||||
if () {
|
||||
Intent viewFile = new Intent(Intent.ACTION_VIEW);
|
||||
viewFile.setData(mOutputUri);
|
||||
viewFile.setInputData(mOutputUri);
|
||||
startActivity(viewFile);
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -27,9 +27,9 @@ import android.widget.TextView;
|
||||
|
||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
|
||||
public abstract class DecryptFragment extends Fragment {
|
||||
|
||||
+8
-6
@@ -20,22 +20,21 @@ package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.api.OpenKeychainIntents;
|
||||
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.SingletonResult;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
public class DecryptTextActivity extends ActionBarActivity {
|
||||
public class DecryptTextActivity extends BaseActivity {
|
||||
|
||||
/* Intents */
|
||||
public static final String ACTION_DECRYPT_TEXT = OpenKeychainIntents.DECRYPT_TEXT;
|
||||
@@ -50,12 +49,15 @@ public class DecryptTextActivity extends ActionBarActivity {
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.decrypt_text_activity);
|
||||
|
||||
// Handle intent actions
|
||||
handleActions(savedInstanceState, getIntent());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initLayout() {
|
||||
setContentView(R.layout.decrypt_text_activity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixing broken PGP MESSAGE Strings coming from GMail/AOSP Mail
|
||||
*/
|
||||
|
||||
+16
-3
@@ -34,13 +34,15 @@ import org.openintents.openpgp.util.OpenPgpApi;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
|
||||
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.ShareHelper;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
public class DecryptTextFragment extends DecryptFragment {
|
||||
public static final String ARG_CIPHERTEXT = "ciphertext";
|
||||
|
||||
@@ -111,7 +113,7 @@ public class DecryptTextFragment extends DecryptFragment {
|
||||
Intent prototype = createSendIntent(text);
|
||||
String title = getString(R.string.title_share_file);
|
||||
|
||||
// we don't want to decrypt the decypted, no inception ;)
|
||||
// we don't want to decrypt the decrypted, no inception ;)
|
||||
String[] blacklist = new String[]{
|
||||
Constants.PACKAGE_NAME + ".ui.DecryptTextActivity",
|
||||
"org.thialfihar.android.apg.ui.DecryptActivity"
|
||||
@@ -194,7 +196,18 @@ public class DecryptTextFragment extends DecryptFragment {
|
||||
|
||||
byte[] decryptedMessage = returnData
|
||||
.getByteArray(KeychainIntentService.RESULT_DECRYPTED_BYTES);
|
||||
mText.setText(new String(decryptedMessage));
|
||||
String displayMessage;
|
||||
if (pgpResult.getCharset() != null) {
|
||||
try {
|
||||
displayMessage = new String(decryptedMessage, pgpResult.getCharset());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// if we can't decode properly, just fall back to utf-8
|
||||
displayMessage = new String(decryptedMessage);
|
||||
}
|
||||
} else {
|
||||
displayMessage = new String(decryptedMessage);
|
||||
}
|
||||
mText.setText(displayMessage);
|
||||
|
||||
pgpResult.createNotify(getActivity()).show();
|
||||
|
||||
|
||||
@@ -1,306 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.ActionBarDrawerToggle;
|
||||
import android.support.v4.view.GravityCompat;
|
||||
import android.support.v4.widget.DrawerLayout;
|
||||
import android.support.v4.widget.FixedDrawerLayout;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
|
||||
public class DrawerActivity extends ActionBarActivity {
|
||||
private FixedDrawerLayout mDrawerLayout;
|
||||
private ListView mDrawerList;
|
||||
private ActionBarDrawerToggle mDrawerToggle;
|
||||
|
||||
private CharSequence mDrawerTitle;
|
||||
private CharSequence mTitle;
|
||||
private boolean mIsDrawerLocked = false;
|
||||
|
||||
private Class mSelectedItem;
|
||||
|
||||
private static final int MENU_ID_PREFERENCE = 222;
|
||||
private static final int MENU_ID_HELP = 223;
|
||||
|
||||
protected void deactivateDrawerNavigation() {
|
||||
((DrawerLayout) findViewById(R.id.drawer_layout)).setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
|
||||
}
|
||||
|
||||
protected void activateDrawerNavigation(Bundle savedInstanceState) {
|
||||
mDrawerTitle = getString(R.string.app_name);
|
||||
mDrawerLayout = (FixedDrawerLayout) findViewById(R.id.drawer_layout);
|
||||
mDrawerList = (ListView) findViewById(R.id.left_drawer);
|
||||
ViewGroup viewGroup = (ViewGroup) findViewById(R.id.content_frame);
|
||||
int leftMarginLoaded = ((ViewGroup.MarginLayoutParams) viewGroup.getLayoutParams()).leftMargin;
|
||||
int leftMarginInTablets = (int) getResources().getDimension(R.dimen.drawer_size);
|
||||
int errorInMarginAllowed = 5;
|
||||
|
||||
// if the left margin of the loaded layout is close to the
|
||||
// one used in tablets then set drawer as open and locked
|
||||
if (Math.abs(leftMarginLoaded - leftMarginInTablets) < errorInMarginAllowed) {
|
||||
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN, mDrawerList);
|
||||
mDrawerLayout.setScrimColor(Color.TRANSPARENT);
|
||||
mIsDrawerLocked = true;
|
||||
} else {
|
||||
// set a custom shadow that overlays the main content when the drawer opens
|
||||
mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
|
||||
mIsDrawerLocked = false;
|
||||
}
|
||||
|
||||
NavItem mItemIconTexts[] = new NavItem[]{
|
||||
new NavItem(R.drawable.ic_action_accounts, getString(R.string.nav_keys)),
|
||||
new NavItem(R.drawable.ic_action_secure, getString(R.string.nav_encrypt_text)),
|
||||
new NavItem(R.drawable.ic_action_secure, getString(R.string.nav_encrypt_files)),
|
||||
new NavItem(R.drawable.ic_action_not_secure, getString(R.string.nav_decrypt)),
|
||||
new NavItem(R.drawable.ic_action_view_as_list, getString(R.string.nav_apps))};
|
||||
|
||||
mDrawerList.setAdapter(new NavigationDrawerAdapter(this, R.layout.drawer_list_item,
|
||||
mItemIconTexts));
|
||||
|
||||
mDrawerList.setOnItemClickListener(new DrawerItemClickListener());
|
||||
|
||||
// enable ActionBar app icon to behave as action to toggle nav drawer
|
||||
// if the drawer is not locked
|
||||
if (!mIsDrawerLocked) {
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setHomeButtonEnabled(true);
|
||||
}
|
||||
|
||||
// ActionBarDrawerToggle ties together the the proper interactions
|
||||
// between the sliding drawer and the action bar app icon
|
||||
mDrawerToggle = new ActionBarDrawerToggle(this, /* host Activity */
|
||||
mDrawerLayout, /* DrawerLayout object */
|
||||
R.drawable.ic_drawer, /* nav drawer image to replace 'Up' caret */
|
||||
R.string.drawer_open, /* "open drawer" description for accessibility */
|
||||
R.string.drawer_close /* "close drawer" description for accessibility */
|
||||
) {
|
||||
public void onDrawerClosed(View view) {
|
||||
getSupportActionBar().setTitle(mTitle);
|
||||
|
||||
callIntentForDrawerItem(mSelectedItem);
|
||||
}
|
||||
|
||||
public void onDrawerOpened(View drawerView) {
|
||||
mTitle = getSupportActionBar().getTitle();
|
||||
getSupportActionBar().setTitle(mDrawerTitle);
|
||||
// creates call to onPrepareOptionsMenu()
|
||||
supportInvalidateOptionsMenu();
|
||||
}
|
||||
};
|
||||
|
||||
if (!mIsDrawerLocked) {
|
||||
mDrawerLayout.setDrawerListener(mDrawerToggle);
|
||||
} else {
|
||||
// If the drawer is locked open make it un-focusable
|
||||
// so that it doesn't consume all the Back button presses
|
||||
mDrawerLayout.setFocusableInTouchMode(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses startActivity to call the Intent of the given class
|
||||
*
|
||||
* @param drawerItem the class of the drawer item you want to load. Based on Constants.DrawerItems.*
|
||||
*/
|
||||
public void callIntentForDrawerItem(Class drawerItem) {
|
||||
// creates call to onPrepareOptionsMenu()
|
||||
supportInvalidateOptionsMenu();
|
||||
|
||||
// call intent activity if selected
|
||||
if (drawerItem != null) {
|
||||
finish();
|
||||
overridePendingTransition(0, 0);
|
||||
|
||||
Intent intent = new Intent(this, drawerItem);
|
||||
startActivity(intent);
|
||||
|
||||
// disable animation of activity start
|
||||
overridePendingTransition(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
if (mDrawerToggle == null) {
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
menu.add(42, MENU_ID_PREFERENCE, 100, R.string.menu_preferences);
|
||||
menu.add(42, MENU_ID_HELP, 101, R.string.menu_help);
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (mDrawerToggle == null) {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
// The action bar home/up action should open or close the drawer.
|
||||
// ActionBarDrawerToggle will take care of this.
|
||||
if (mDrawerToggle.onOptionsItemSelected(item)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case MENU_ID_PREFERENCE: {
|
||||
Intent intent = new Intent(this, PreferencesActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
case MENU_ID_HELP: {
|
||||
Intent intent = new Intent(this, HelpActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The click listener for ListView in the navigation drawer
|
||||
*/
|
||||
private class DrawerItemClickListener implements ListView.OnItemClickListener {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
selectItem(position);
|
||||
}
|
||||
}
|
||||
|
||||
private void selectItem(int position) {
|
||||
// update selected item and title, then close the drawer
|
||||
mDrawerList.setItemChecked(position, true);
|
||||
// set selected class
|
||||
mSelectedItem = Constants.DrawerItems.ARRAY[position];
|
||||
|
||||
// setTitle(mDrawerTitles[position]);
|
||||
// If drawer isn't locked just close the drawer and
|
||||
// it will move to the selected item by itself (via drawer toggle listener)
|
||||
if (!mIsDrawerLocked) {
|
||||
mDrawerLayout.closeDrawer(mDrawerList);
|
||||
// else move to the selected item yourself
|
||||
} else {
|
||||
callIntentForDrawerItem(mSelectedItem);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When using the ActionBarDrawerToggle, you must call it during onPostCreate() and
|
||||
* onConfigurationChanged()...
|
||||
*/
|
||||
@Override
|
||||
protected void onPostCreate(Bundle savedInstanceState) {
|
||||
super.onPostCreate(savedInstanceState);
|
||||
// Sync the toggle state after onRestoreInstanceState has occurred.
|
||||
if (mDrawerToggle != null) {
|
||||
mDrawerToggle.syncState();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
// Pass any configuration change to the drawer toggles
|
||||
if (mDrawerToggle != null) {
|
||||
mDrawerToggle.onConfigurationChanged(newConfig);
|
||||
}
|
||||
}
|
||||
|
||||
private class NavItem {
|
||||
public int icon; // res-id
|
||||
public String title;
|
||||
|
||||
/**
|
||||
* NavItem constructor
|
||||
*
|
||||
* @param icon The icons resource-id
|
||||
* @param title The title of the menu entry
|
||||
*/
|
||||
public NavItem(int icon, String title) {
|
||||
super();
|
||||
this.icon = icon;
|
||||
this.title = title;
|
||||
}
|
||||
}
|
||||
|
||||
private class NavigationDrawerAdapter extends ArrayAdapter<NavItem> {
|
||||
Context mContext;
|
||||
int mLayoutResourceId;
|
||||
NavItem mData[] = null;
|
||||
|
||||
public NavigationDrawerAdapter(Context context, int layoutResourceId, NavItem[] data) {
|
||||
super(context, layoutResourceId, data);
|
||||
this.mLayoutResourceId = layoutResourceId;
|
||||
this.mContext = context;
|
||||
this.mData = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
View row = convertView;
|
||||
NavItemHolder holder;
|
||||
|
||||
if (row == null) {
|
||||
LayoutInflater inflater = ((Activity) mContext).getLayoutInflater();
|
||||
row = inflater.inflate(mLayoutResourceId, parent, false);
|
||||
|
||||
holder = new NavItemHolder();
|
||||
holder.mImg = (ImageView) row.findViewById(R.id.drawer_item_icon);
|
||||
holder.mTxtTitle = (TextView) row.findViewById(R.id.drawer_item_text);
|
||||
|
||||
row.setTag(holder);
|
||||
} else {
|
||||
holder = (NavItemHolder) row.getTag();
|
||||
}
|
||||
|
||||
NavItem item = mData[position];
|
||||
holder.mTxtTitle.setText(item.title);
|
||||
holder.mImg.setImageResource(item.icon);
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class NavItemHolder {
|
||||
ImageView mImg;
|
||||
TextView mTxtTitle;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,14 +19,13 @@ package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
public class EditKeyActivity extends ActionBarActivity {
|
||||
public class EditKeyActivity extends BaseActivity {
|
||||
|
||||
public static final String EXTRA_SAVE_KEYRING_PARCEL = "save_keyring_parcel";
|
||||
|
||||
@@ -36,8 +35,6 @@ public class EditKeyActivity extends ActionBarActivity {
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.edit_key_activity);
|
||||
|
||||
Uri dataUri = getIntent().getData();
|
||||
SaveKeyringParcel saveKeyringParcel = getIntent().getParcelableExtra(EXTRA_SAVE_KEYRING_PARCEL);
|
||||
if (dataUri == null && saveKeyringParcel == null) {
|
||||
@@ -49,6 +46,11 @@ public class EditKeyActivity extends ActionBarActivity {
|
||||
loadFragment(savedInstanceState, dataUri, saveKeyringParcel);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initLayout() {
|
||||
setContentView(R.layout.edit_key_activity);
|
||||
}
|
||||
|
||||
private void loadFragment(Bundle savedInstanceState, Uri dataUri, SaveKeyringParcel saveKeyringParcel) {
|
||||
// However, if we're being restored from a previous state,
|
||||
// then we don't need to do anything and should return or else
|
||||
|
||||
+14
-22
@@ -29,7 +29,6 @@ import android.os.Messenger;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
@@ -55,6 +54,7 @@ import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.SubkeysAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.SubkeysAddedAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;
|
||||
@@ -65,7 +65,6 @@ import org.sufficientlysecure.keychain.ui.dialog.EditSubkeyDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.EditSubkeyExpiryDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.EditUserIdDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.ActionBarHelper;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
@@ -147,10 +146,8 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
// Inflate a "Done"/"Cancel" custom action bar view
|
||||
ActionBarHelper.setTwoButtonView(((ActionBarActivity) getActivity()).getSupportActionBar(),
|
||||
R.string.btn_save, R.drawable.ic_action_save,
|
||||
((EditKeyActivity) getActivity()).setFullScreenDialogDoneClose(
|
||||
R.string.btn_save,
|
||||
new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
@@ -161,16 +158,13 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
saveInDatabase(mCurrentPassphrase);
|
||||
}
|
||||
}
|
||||
}, R.string.menu_key_edit_cancel, R.drawable.ic_action_cancel,
|
||||
new OnClickListener() {
|
||||
}, new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// cancel
|
||||
getActivity().setResult(Activity.RESULT_CANCELED);
|
||||
getActivity().finish();
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
Uri dataUri = getArguments().getParcelable(ARG_DATA_URI);
|
||||
SaveKeyringParcel saveKeyringParcel = getArguments().getParcelable(ARG_SAVE_KEYRING_PARCEL);
|
||||
@@ -230,10 +224,7 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
mSaveKeyringParcel = new SaveKeyringParcel(masterKeyId, keyRing.getFingerprint());
|
||||
mPrimaryUserId = keyRing.getPrimaryUserIdWithFallback();
|
||||
|
||||
} catch (PgpKeyNotFoundException e) {
|
||||
finishWithError(LogType.MSG_EK_ERROR_NOT_FOUND);
|
||||
return;
|
||||
} catch (NotFoundException e) {
|
||||
} catch (PgpKeyNotFoundException | NotFoundException e) {
|
||||
finishWithError(LogType.MSG_EK_ERROR_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
@@ -395,8 +386,8 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
|
||||
// cache new returned passphrase!
|
||||
mSaveKeyringParcel.mNewUnlock = new ChangeUnlockParcel(
|
||||
data.getString(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE),
|
||||
null
|
||||
data.getString(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE),
|
||||
null
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -478,12 +469,13 @@ public class EditKeyFragment extends LoaderFragment implements
|
||||
}
|
||||
break;
|
||||
case EditSubkeyDialogFragment.MESSAGE_STRIP:
|
||||
// toggle
|
||||
if (mSaveKeyringParcel.mStripSubKeys.contains(keyId)) {
|
||||
mSaveKeyringParcel.mStripSubKeys.remove(keyId);
|
||||
} else {
|
||||
mSaveKeyringParcel.mStripSubKeys.add(keyId);
|
||||
SubkeyChange change = mSaveKeyringParcel.getSubkeyChange(keyId);
|
||||
if (change == null) {
|
||||
mSaveKeyringParcel.mChangeSubKeys.add(new SubkeyChange(keyId, true, null));
|
||||
break;
|
||||
}
|
||||
// toggle
|
||||
change.mDummyStrip = !change.mDummyStrip;
|
||||
break;
|
||||
}
|
||||
getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad();
|
||||
|
||||
+23
-15
@@ -8,13 +8,15 @@ import android.os.Messenger;
|
||||
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
|
||||
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public abstract class EncryptActivity extends DrawerActivity {
|
||||
public abstract class EncryptActivity extends BaseActivity {
|
||||
|
||||
public static final int REQUEST_CODE_PASSPHRASE = 0x00008001;
|
||||
public static final int REQUEST_CODE_NFC = 0x00008002;
|
||||
@@ -82,7 +84,10 @@ public abstract class EncryptActivity extends DrawerActivity {
|
||||
// Send all information needed to service to edit key in other thread
|
||||
Intent intent = new Intent(this, KeychainIntentService.class);
|
||||
intent.setAction(KeychainIntentService.ACTION_SIGN_ENCRYPT);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, createEncryptBundle());
|
||||
|
||||
Bundle data = new Bundle();
|
||||
data.putParcelable(KeychainIntentService.SIGN_ENCRYPT_PARCEL, createEncryptBundle());
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after encrypting is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler serviceHandler = new KeychainIntentServiceHandler(this,
|
||||
@@ -92,28 +97,31 @@ public abstract class EncryptActivity extends DrawerActivity {
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
SignEncryptResult pgpResult =
|
||||
SignEncryptResult result =
|
||||
message.getData().getParcelable(SignEncryptResult.EXTRA_RESULT);
|
||||
|
||||
if (pgpResult.isPending()) {
|
||||
if ((pgpResult.getResult() & SignEncryptResult.RESULT_PENDING_PASSPHRASE) ==
|
||||
SignEncryptResult.RESULT_PENDING_PASSPHRASE) {
|
||||
PgpSignEncryptResult pgpResult = result.getPending();
|
||||
|
||||
if (pgpResult != null && pgpResult.isPending()) {
|
||||
if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE) ==
|
||||
PgpSignEncryptResult.RESULT_PENDING_PASSPHRASE) {
|
||||
startPassphraseDialog(pgpResult.getKeyIdPassphraseNeeded());
|
||||
} else if ((pgpResult.getResult() & SignEncryptResult.RESULT_PENDING_NFC) ==
|
||||
SignEncryptResult.RESULT_PENDING_NFC) {
|
||||
} else if ((pgpResult.getResult() & PgpSignEncryptResult.RESULT_PENDING_NFC) ==
|
||||
PgpSignEncryptResult.RESULT_PENDING_NFC) {
|
||||
|
||||
mNfcTimestamp = pgpResult.getNfcTimestamp();
|
||||
startNfcSign(pgpResult.getNfcKeyId(), pgpResult.getNfcPassphrase(), pgpResult.getNfcHash(), pgpResult.getNfcAlgo());
|
||||
startNfcSign(pgpResult.getNfcKeyId(), pgpResult.getNfcPassphrase(),
|
||||
pgpResult.getNfcHash(), pgpResult.getNfcAlgo());
|
||||
} else {
|
||||
throw new RuntimeException("Unhandled pending result!");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (pgpResult.success()) {
|
||||
onEncryptSuccess(message, pgpResult);
|
||||
if (result.success()) {
|
||||
onEncryptSuccess(result);
|
||||
} else {
|
||||
pgpResult.createNotify(EncryptActivity.this).show();
|
||||
result.createNotify(EncryptActivity.this).show();
|
||||
}
|
||||
|
||||
// no matter the result, reset parameters
|
||||
@@ -136,8 +144,8 @@ public abstract class EncryptActivity extends DrawerActivity {
|
||||
|
||||
protected abstract boolean inputIsValid();
|
||||
|
||||
protected abstract void onEncryptSuccess(Message message, SignEncryptResult result);
|
||||
protected abstract void onEncryptSuccess(SignEncryptResult result);
|
||||
|
||||
protected abstract Bundle createEncryptBundle();
|
||||
protected abstract SignEncryptParcel createEncryptBundle();
|
||||
|
||||
}
|
||||
|
||||
+2
-3
@@ -28,7 +28,6 @@ import com.tokenautocomplete.TokenCompleteTextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
@@ -164,8 +163,8 @@ public class EncryptAsymmetricFragment extends Fragment implements EncryptActivi
|
||||
|
||||
private void updateEncryptionKeys() {
|
||||
List<Object> objects = mEncryptKeyView.getObjects();
|
||||
List<Long> keyIds = new ArrayList<Long>();
|
||||
List<String> userIds = new ArrayList<String>();
|
||||
List<Long> keyIds = new ArrayList<>();
|
||||
List<String> userIds = new ArrayList<>();
|
||||
for (Object object : objects) {
|
||||
if (object instanceof EncryptKeyCompletionView.EncryptionKey) {
|
||||
keyIds.add(((EncryptKeyCompletionView.EncryptionKey) object).getKeyId());
|
||||
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* 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
|
||||
* 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.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
import org.sufficientlysecure.keychain.ui.util.SubtleAttentionSeeker;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
public class EncryptDecryptOverviewFragment extends Fragment {
|
||||
|
||||
View mEncryptFile;
|
||||
View mEncryptText;
|
||||
View mDecryptFile;
|
||||
View mDecryptFromClipboard;
|
||||
View mClipboardIcon;
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.encrypt_decrypt_overview_fragment, container, false);
|
||||
|
||||
mEncryptFile = view.findViewById(R.id.encrypt_files);
|
||||
mEncryptText = view.findViewById(R.id.encrypt_text);
|
||||
mDecryptFile = view.findViewById(R.id.decrypt_files);
|
||||
mDecryptFromClipboard = view.findViewById(R.id.decrypt_from_clipboard);
|
||||
mClipboardIcon = view.findViewById(R.id.clipboard_icon);
|
||||
|
||||
mEncryptFile.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent encrypt = new Intent(getActivity(), EncryptFilesActivity.class);
|
||||
startActivity(encrypt);
|
||||
}
|
||||
});
|
||||
|
||||
mEncryptText.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent encrypt = new Intent(getActivity(), EncryptTextActivity.class);
|
||||
startActivity(encrypt);
|
||||
}
|
||||
});
|
||||
|
||||
mDecryptFile.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent filesDecrypt = new Intent(getActivity(), DecryptFilesActivity.class);
|
||||
filesDecrypt.setAction(DecryptFilesActivity.ACTION_DECRYPT_DATA_OPEN);
|
||||
startActivity(filesDecrypt);
|
||||
}
|
||||
});
|
||||
|
||||
mDecryptFromClipboard.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent clipboardDecrypt = new Intent(getActivity(), DecryptTextActivity.class);
|
||||
clipboardDecrypt.setAction(DecryptTextActivity.ACTION_DECRYPT_FROM_CLIPBOARD);
|
||||
startActivityForResult(clipboardDecrypt, 0);
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
// get text from clipboard
|
||||
final CharSequence clipboardText =
|
||||
ClipboardReflection.getClipboardText(getActivity());
|
||||
|
||||
// if it's null, nothing to do here /o/
|
||||
if (clipboardText == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
new AsyncTask<String, Void, Boolean>() {
|
||||
@Override
|
||||
protected Boolean doInBackground(String... clipboardText) {
|
||||
|
||||
// see if it looks like a pgp thing
|
||||
Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(clipboardText[0]);
|
||||
boolean animate = matcher.matches();
|
||||
|
||||
// see if it looks like another pgp thing
|
||||
if (!animate) {
|
||||
matcher = PgpHelper.PGP_CLEARTEXT_SIGNATURE.matcher(clipboardText[0]);
|
||||
animate = matcher.matches();
|
||||
}
|
||||
return animate;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean animate) {
|
||||
super.onPostExecute(animate);
|
||||
|
||||
// if so, animate the clipboard icon just a bit~
|
||||
if (animate) {
|
||||
SubtleAttentionSeeker.tada(mClipboardIcon, 1.5f).start();
|
||||
}
|
||||
}
|
||||
}.execute(clipboardText.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
// if a result has been returned, display a notify
|
||||
if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) {
|
||||
OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT);
|
||||
result.createNotify(getActivity()).show();
|
||||
} else {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
-37
@@ -21,7 +21,6 @@ package org.sufficientlysecure.keychain.ui;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
@@ -29,14 +28,14 @@ import android.view.MenuItem;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.api.OpenKeychainIntents;
|
||||
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
import org.sufficientlysecure.keychain.util.ShareHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
@@ -122,13 +121,13 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi
|
||||
|
||||
@Override
|
||||
public ArrayList<Uri> getInputUris() {
|
||||
if (mInputUris == null) mInputUris = new ArrayList<Uri>();
|
||||
if (mInputUris == null) mInputUris = new ArrayList<>();
|
||||
return mInputUris;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<Uri> getOutputUris() {
|
||||
if (mOutputUris == null) mOutputUris = new ArrayList<Uri>();
|
||||
if (mOutputUris == null) mOutputUris = new ArrayList<>();
|
||||
return mOutputUris;
|
||||
}
|
||||
|
||||
@@ -170,7 +169,7 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEncryptSuccess(Message message, SignEncryptResult pgpResult) {
|
||||
public void onEncryptSuccess(SignEncryptResult result) {
|
||||
if (mDeleteAfterEncrypt) {
|
||||
for (Uri inputUri : mInputUris) {
|
||||
DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(inputUri);
|
||||
@@ -182,29 +181,25 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi
|
||||
|
||||
if (mShareAfterEncrypt) {
|
||||
// Share encrypted message/file
|
||||
startActivity(sendWithChooserExcludingEncrypt(message));
|
||||
startActivity(sendWithChooserExcludingEncrypt());
|
||||
} else {
|
||||
// Save encrypted file
|
||||
pgpResult.createNotify(EncryptFilesActivity.this).show();
|
||||
result.createNotify(EncryptFilesActivity.this).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bundle createEncryptBundle() {
|
||||
protected SignEncryptParcel createEncryptBundle() {
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
SignEncryptParcel data = new SignEncryptParcel();
|
||||
|
||||
data.putInt(KeychainIntentService.SOURCE, KeychainIntentService.IO_URIS);
|
||||
data.putParcelableArrayList(KeychainIntentService.ENCRYPT_INPUT_URIS, mInputUris);
|
||||
data.addInputUris(mInputUris);
|
||||
data.addOutputUris(mOutputUris);
|
||||
|
||||
data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_URIS);
|
||||
data.putParcelableArrayList(KeychainIntentService.ENCRYPT_OUTPUT_URIS, mOutputUris);
|
||||
|
||||
data.putInt(KeychainIntentService.ENCRYPT_COMPRESSION_ID,
|
||||
Preferences.getPreferences(this).getDefaultFileCompression());
|
||||
data.setCompressionId(Preferences.getPreferences(this).getDefaultMessageCompression());
|
||||
|
||||
// Always use armor for messages
|
||||
data.putBoolean(KeychainIntentService.ENCRYPT_USE_ASCII_ARMOR, mUseArmor);
|
||||
data.setEnableAsciiArmorOutput(mUseArmor);
|
||||
|
||||
if (isModeSymmetric()) {
|
||||
Log.d(Constants.TAG, "Symmetric encryption enabled!");
|
||||
@@ -212,13 +207,12 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi
|
||||
if (passphrase.length() == 0) {
|
||||
passphrase = null;
|
||||
}
|
||||
data.putString(KeychainIntentService.ENCRYPT_SYMMETRIC_PASSPHRASE, passphrase);
|
||||
data.setSymmetricPassphrase(passphrase);
|
||||
} else {
|
||||
data.putLong(KeychainIntentService.ENCRYPT_SIGNATURE_MASTER_ID, mSigningKeyId);
|
||||
data.putLongArray(KeychainIntentService.ENCRYPT_ENCRYPTION_KEYS_IDS, mEncryptionKeyIds);
|
||||
data.putString(KeychainIntentService.ENCRYPT_SIGNATURE_KEY_PASSPHRASE, mSigningKeyPassphrase);
|
||||
data.putSerializable(KeychainIntentService.ENCRYPT_SIGNATURE_NFC_TIMESTAMP, mNfcTimestamp);
|
||||
data.putByteArray(KeychainIntentService.ENCRYPT_SIGNATURE_NFC_HASH, mNfcHash);
|
||||
data.setEncryptionMasterKeyIds(mEncryptionKeyIds);
|
||||
data.setSignatureMasterKeyId(mSigningKeyId);
|
||||
data.setSignaturePassphrase(mSigningKeyPassphrase);
|
||||
data.setNfcState(mNfcHash, mNfcTimestamp);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
@@ -226,8 +220,8 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi
|
||||
/**
|
||||
* Create Intent Chooser but exclude OK's EncryptActivity.
|
||||
*/
|
||||
private Intent sendWithChooserExcludingEncrypt(Message message) {
|
||||
Intent prototype = createSendIntent(message);
|
||||
private Intent sendWithChooserExcludingEncrypt() {
|
||||
Intent prototype = createSendIntent();
|
||||
String title = getString(R.string.title_share_file);
|
||||
|
||||
// we don't want to encrypt the encrypted, no inception ;)
|
||||
@@ -239,7 +233,7 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi
|
||||
return new ShareHelper(this).createChooserExcluding(prototype, title, blacklist);
|
||||
}
|
||||
|
||||
private Intent createSendIntent(Message message) {
|
||||
private Intent createSendIntent() {
|
||||
Intent sendIntent;
|
||||
// file
|
||||
if (mOutputUris.size() == 1) {
|
||||
@@ -252,7 +246,7 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi
|
||||
sendIntent.setType("application/octet-stream");
|
||||
|
||||
if (!isModeSymmetric() && mEncryptionUserIds != null) {
|
||||
Set<String> users = new HashSet<String>();
|
||||
Set<String> users = new HashSet<>();
|
||||
for (String user : mEncryptionUserIds) {
|
||||
String[] userId = KeyRing.splitUserId(user);
|
||||
if (userId[1] != null) {
|
||||
@@ -309,15 +303,13 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.encrypt_files_activity);
|
||||
|
||||
// if called with an intent action, do not init drawer navigation
|
||||
if (ACTION_ENCRYPT_DATA.equals(getIntent().getAction())) {
|
||||
// lock drawer
|
||||
deactivateDrawerNavigation();
|
||||
// deactivateDrawerNavigation();
|
||||
// TODO: back button to key?
|
||||
} else {
|
||||
activateDrawerNavigation(savedInstanceState);
|
||||
// activateDrawerNavigation(savedInstanceState);
|
||||
}
|
||||
|
||||
// Handle intent actions
|
||||
@@ -327,6 +319,11 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi
|
||||
mUseArmor = Preferences.getPreferences(this).getDefaultAsciiArmor();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initLayout() {
|
||||
setContentView(R.layout.encrypt_files_activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.encrypt_file_activity, menu);
|
||||
@@ -379,7 +376,7 @@ public class EncryptFilesActivity extends EncryptActivity implements EncryptActi
|
||||
String action = intent.getAction();
|
||||
Bundle extras = intent.getExtras();
|
||||
String type = intent.getType();
|
||||
ArrayList<Uri> uris = new ArrayList<Uri>();
|
||||
ArrayList<Uri> uris = new ArrayList<>();
|
||||
|
||||
if (extras == null) {
|
||||
extras = new Bundle();
|
||||
|
||||
+3
-3
@@ -36,10 +36,10 @@ import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
|
||||
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.util.FileHelper;
|
||||
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
@@ -59,7 +59,7 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt
|
||||
private View mShareFile;
|
||||
private ListView mSelectedFiles;
|
||||
private SelectedFilesAdapter mAdapter = new SelectedFilesAdapter();
|
||||
private final Map<Uri, Bitmap> thumbnailCache = new HashMap<Uri, Bitmap>();
|
||||
private final Map<Uri, Bitmap> thumbnailCache = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
@@ -224,7 +224,7 @@ public class EncryptFilesFragment extends Fragment implements EncryptActivityInt
|
||||
@Override
|
||||
public void onNotifyUpdate() {
|
||||
// Clear cache if needed
|
||||
for (Uri uri : new HashSet<Uri>(thumbnailCache.keySet())) {
|
||||
for (Uri uri : new HashSet<>(thumbnailCache.keySet())) {
|
||||
if (!mEncryptInterface.getInputUris().contains(uri)) {
|
||||
thumbnailCache.remove(uri);
|
||||
}
|
||||
|
||||
+37
-36
@@ -21,7 +21,6 @@ package org.sufficientlysecure.keychain.ui;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
@@ -30,13 +29,13 @@ import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.api.OpenKeychainIntents;
|
||||
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
|
||||
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
import org.sufficientlysecure.keychain.util.ShareHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.operations.results.SignEncryptResult;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
@@ -121,13 +120,13 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv
|
||||
|
||||
@Override
|
||||
public ArrayList<Uri> getInputUris() {
|
||||
if (mInputUris == null) mInputUris = new ArrayList<Uri>();
|
||||
if (mInputUris == null) mInputUris = new ArrayList<>();
|
||||
return mInputUris;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<Uri> getOutputUris() {
|
||||
if (mOutputUris == null) mOutputUris = new ArrayList<Uri>();
|
||||
if (mOutputUris == null) mOutputUris = new ArrayList<>();
|
||||
return mOutputUris;
|
||||
}
|
||||
|
||||
@@ -169,32 +168,31 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEncryptSuccess(Message message, SignEncryptResult pgpResult) {
|
||||
protected void onEncryptSuccess(SignEncryptResult result) {
|
||||
if (mShareAfterEncrypt) {
|
||||
// Share encrypted message/file
|
||||
startActivity(sendWithChooserExcludingEncrypt(message));
|
||||
startActivity(sendWithChooserExcludingEncrypt(result.getResultBytes()));
|
||||
} else {
|
||||
// Copy to clipboard
|
||||
copyToClipboard(message);
|
||||
pgpResult.createNotify(EncryptTextActivity.this).show();
|
||||
copyToClipboard(result.getResultBytes());
|
||||
result.createNotify(EncryptTextActivity.this).show();
|
||||
// Notify.showNotify(EncryptTextActivity.this,
|
||||
// R.string.encrypt_sign_clipboard_successful, Notify.Style.INFO);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bundle createEncryptBundle() {
|
||||
protected SignEncryptParcel createEncryptBundle() {
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
SignEncryptParcel data = new SignEncryptParcel();
|
||||
|
||||
data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_BYTES);
|
||||
data.putByteArray(KeychainIntentService.ENCRYPT_MESSAGE_BYTES, mMessage.getBytes());
|
||||
data.setBytes(mMessage.getBytes());
|
||||
data.setCleartextSignature(true);
|
||||
|
||||
data.putInt(KeychainIntentService.ENCRYPT_COMPRESSION_ID,
|
||||
Preferences.getPreferences(this).getDefaultMessageCompression());
|
||||
data.setCompressionId(Preferences.getPreferences(this).getDefaultMessageCompression());
|
||||
|
||||
// Always use armor for messages
|
||||
data.putBoolean(KeychainIntentService.ENCRYPT_USE_ASCII_ARMOR, true);
|
||||
data.setEnableAsciiArmorOutput(true);
|
||||
|
||||
if (isModeSymmetric()) {
|
||||
Log.d(Constants.TAG, "Symmetric encryption enabled!");
|
||||
@@ -202,26 +200,25 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv
|
||||
if (passphrase.length() == 0) {
|
||||
passphrase = null;
|
||||
}
|
||||
data.putString(KeychainIntentService.ENCRYPT_SYMMETRIC_PASSPHRASE, passphrase);
|
||||
data.setSymmetricPassphrase(passphrase);
|
||||
} else {
|
||||
data.putLong(KeychainIntentService.ENCRYPT_SIGNATURE_MASTER_ID, mSigningKeyId);
|
||||
data.putLongArray(KeychainIntentService.ENCRYPT_ENCRYPTION_KEYS_IDS, mEncryptionKeyIds);
|
||||
data.putString(KeychainIntentService.ENCRYPT_SIGNATURE_KEY_PASSPHRASE, mSigningKeyPassphrase);
|
||||
data.putSerializable(KeychainIntentService.ENCRYPT_SIGNATURE_NFC_TIMESTAMP, mNfcTimestamp);
|
||||
data.putByteArray(KeychainIntentService.ENCRYPT_SIGNATURE_NFC_HASH, mNfcHash);
|
||||
data.setEncryptionMasterKeyIds(mEncryptionKeyIds);
|
||||
data.setSignatureMasterKeyId(mSigningKeyId);
|
||||
data.setSignaturePassphrase(mSigningKeyPassphrase);
|
||||
data.setNfcState(mNfcHash, mNfcTimestamp);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
private void copyToClipboard(Message message) {
|
||||
ClipboardReflection.copyToClipboard(this, new String(message.getData().getByteArray(KeychainIntentService.RESULT_BYTES)));
|
||||
private void copyToClipboard(byte[] resultBytes) {
|
||||
ClipboardReflection.copyToClipboard(this, new String(resultBytes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Intent Chooser but exclude OK's EncryptActivity.
|
||||
*/
|
||||
private Intent sendWithChooserExcludingEncrypt(Message message) {
|
||||
Intent prototype = createSendIntent(message);
|
||||
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 ;)
|
||||
@@ -233,20 +230,21 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv
|
||||
return new ShareHelper(this).createChooserExcluding(prototype, title, blacklist);
|
||||
}
|
||||
|
||||
private Intent createSendIntent(Message message) {
|
||||
private Intent createSendIntent(byte[] resultBytes) {
|
||||
Intent sendIntent;
|
||||
sendIntent = new Intent(Intent.ACTION_SEND);
|
||||
sendIntent.setType("text/plain");
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, new String(message.getData().getByteArray(KeychainIntentService.RESULT_BYTES)));
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, new String(resultBytes));
|
||||
|
||||
if (!isModeSymmetric() && mEncryptionUserIds != null) {
|
||||
Set<String> users = new HashSet<String>();
|
||||
Set<String> users = new HashSet<>();
|
||||
for (String user : mEncryptionUserIds) {
|
||||
String[] userId = KeyRing.splitUserId(user);
|
||||
if (userId[1] != null) {
|
||||
users.add(userId[1]);
|
||||
}
|
||||
}
|
||||
// pass trough email addresses as extra for email applications
|
||||
sendIntent.putExtra(Intent.EXTRA_EMAIL, users.toArray(new String[users.size()]));
|
||||
}
|
||||
return sendIntent;
|
||||
@@ -288,15 +286,13 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.encrypt_text_activity);
|
||||
|
||||
// if called with an intent action, do not init drawer navigation
|
||||
if (ACTION_ENCRYPT_TEXT.equals(getIntent().getAction())) {
|
||||
// lock drawer
|
||||
deactivateDrawerNavigation();
|
||||
// deactivateDrawerNavigation();
|
||||
// TODO: back button to key?
|
||||
} else {
|
||||
activateDrawerNavigation(savedInstanceState);
|
||||
// activateDrawerNavigation(savedInstanceState);
|
||||
}
|
||||
|
||||
// Handle intent actions
|
||||
@@ -304,6 +300,11 @@ public class EncryptTextActivity extends EncryptActivity implements EncryptActiv
|
||||
updateModeFragment();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initLayout() {
|
||||
setContentView(R.layout.encrypt_text_activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.encrypt_text_activity, menu);
|
||||
|
||||
+7
-6
@@ -19,17 +19,16 @@ package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
public class FirstTimeActivity extends ActionBarActivity {
|
||||
public class FirstTimeActivity extends BaseActivity {
|
||||
|
||||
View mCreateKey;
|
||||
View mImportKey;
|
||||
@@ -43,8 +42,6 @@ public class FirstTimeActivity extends ActionBarActivity {
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.first_time_activity);
|
||||
|
||||
mCreateKey = findViewById(R.id.first_time_create_key);
|
||||
mImportKey = findViewById(R.id.first_time_import_key);
|
||||
mSkipSetup = findViewById(R.id.first_time_cancel);
|
||||
@@ -72,7 +69,11 @@ public class FirstTimeActivity extends ActionBarActivity {
|
||||
startActivityForResult(intent, REQUEST_CODE_CREATE_OR_IMPORT_KEY);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initLayout() {
|
||||
setContentView(R.layout.first_time_activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -91,7 +92,7 @@ public class FirstTimeActivity extends ActionBarActivity {
|
||||
private void finishSetup(Intent srcData) {
|
||||
Preferences prefs = Preferences.getPreferences(this);
|
||||
prefs.setFirstTime(false);
|
||||
Intent intent = new Intent(this, KeyListActivity.class);
|
||||
Intent intent = new Intent(this, MainActivity.class);
|
||||
// give intent through to display notify
|
||||
if (srcData != null) {
|
||||
intent.putExtras(srcData);
|
||||
|
||||
@@ -20,14 +20,14 @@ package org.sufficientlysecure.keychain.ui;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.View;
|
||||
|
||||
import com.astuetz.PagerSlidingTabStrip;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout;
|
||||
|
||||
public class HelpActivity extends ActionBarActivity {
|
||||
public class HelpActivity extends BaseActivity {
|
||||
public static final String EXTRA_SELECTED_TAB = "selected_tab";
|
||||
|
||||
public static final int TAB_START = 0;
|
||||
@@ -44,16 +44,16 @@ public class HelpActivity extends ActionBarActivity {
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
final ActionBar actionBar = getSupportActionBar();
|
||||
actionBar.setDisplayShowTitleEnabled(true);
|
||||
actionBar.setDisplayHomeAsUpEnabled(false);
|
||||
actionBar.setHomeButtonEnabled(false);
|
||||
|
||||
setContentView(R.layout.help_activity);
|
||||
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
mViewPager = (ViewPager) findViewById(R.id.pager);
|
||||
SlidingTabLayout slidingTabLayout =
|
||||
(SlidingTabLayout) findViewById(R.id.sliding_tab_layout);
|
||||
PagerSlidingTabStrip slidingTabLayout =
|
||||
(PagerSlidingTabStrip) findViewById(R.id.sliding_tab_layout);
|
||||
|
||||
mTabsAdapter = new PagerTabStripAdapter(this);
|
||||
mViewPager.setAdapter(mTabsAdapter);
|
||||
@@ -98,4 +98,9 @@ public class HelpActivity extends ActionBarActivity {
|
||||
// switch to tab selected by extra
|
||||
mViewPager.setCurrentItem(selectedTab);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initLayout() {
|
||||
setContentView(R.layout.help_activity);
|
||||
}
|
||||
}
|
||||
|
||||
+96
-139
@@ -17,19 +17,13 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.nfc.NdefMessage;
|
||||
import android.nfc.NfcAdapter;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.Parcelable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
|
||||
@@ -51,7 +45,7 @@ import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class ImportKeysActivity extends ActionBarActivity {
|
||||
public class ImportKeysActivity extends BaseActivity {
|
||||
public static final String ACTION_IMPORT_KEY = OpenKeychainIntents.IMPORT_KEY;
|
||||
public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER = OpenKeychainIntents.IMPORT_KEY_FROM_KEYSERVER;
|
||||
public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT =
|
||||
@@ -64,9 +58,6 @@ public class ImportKeysActivity extends ActionBarActivity {
|
||||
// Actions for internal use only:
|
||||
public static final String ACTION_IMPORT_KEY_FROM_FILE = Constants.INTENT_PREFIX
|
||||
+ "IMPORT_KEY_FROM_FILE";
|
||||
public static final String ACTION_IMPORT_KEY_FROM_NFC = Constants.INTENT_PREFIX
|
||||
+ "IMPORT_KEY_FROM_NFC";
|
||||
|
||||
public static final String EXTRA_RESULT = "result";
|
||||
|
||||
// only used by ACTION_IMPORT_KEY
|
||||
@@ -90,8 +81,6 @@ public class ImportKeysActivity extends ActionBarActivity {
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.import_keys_activity);
|
||||
|
||||
mImportButton = findViewById(R.id.import_import);
|
||||
mImportButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
@@ -103,6 +92,11 @@ public class ImportKeysActivity extends ActionBarActivity {
|
||||
handleActions(savedInstanceState, getIntent());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initLayout() {
|
||||
setContentView(R.layout.import_keys_activity);
|
||||
}
|
||||
|
||||
protected void handleActions(Bundle savedInstanceState, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
Bundle extras = intent.getExtras();
|
||||
@@ -113,105 +107,114 @@ public class ImportKeysActivity extends ActionBarActivity {
|
||||
extras = new Bundle();
|
||||
}
|
||||
|
||||
if (action == null) {
|
||||
startCloudFragment(savedInstanceState, null, false);
|
||||
startListFragment(savedInstanceState, null, null, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Intent.ACTION_VIEW.equals(action)) {
|
||||
// Android's Action when opening file associated to Keychain (see AndroidManifest.xml)
|
||||
// delegate action to ACTION_IMPORT_KEY
|
||||
action = ACTION_IMPORT_KEY;
|
||||
}
|
||||
|
||||
if (ACTION_IMPORT_KEY.equals(action)) {
|
||||
/* Keychain's own Actions */
|
||||
startFileFragment(savedInstanceState);
|
||||
switch (action) {
|
||||
case ACTION_IMPORT_KEY: {
|
||||
/* Keychain's own Actions */
|
||||
startFileFragment(savedInstanceState);
|
||||
|
||||
if (dataUri != null) {
|
||||
// action: directly load data
|
||||
startListFragment(savedInstanceState, null, dataUri, null);
|
||||
} else if (extras.containsKey(EXTRA_KEY_BYTES)) {
|
||||
byte[] importData = extras.getByteArray(EXTRA_KEY_BYTES);
|
||||
if (dataUri != null) {
|
||||
// action: directly load data
|
||||
startListFragment(savedInstanceState, null, dataUri, null);
|
||||
} else if (extras.containsKey(EXTRA_KEY_BYTES)) {
|
||||
byte[] importData = extras.getByteArray(EXTRA_KEY_BYTES);
|
||||
|
||||
// action: directly load data
|
||||
startListFragment(savedInstanceState, importData, null, null);
|
||||
}
|
||||
} else if (ACTION_IMPORT_KEY_FROM_KEYSERVER.equals(action)
|
||||
|| ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE.equals(action)
|
||||
|| ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT.equals(action)) {
|
||||
|
||||
// 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 */
|
||||
|
||||
String query = null;
|
||||
if (extras.containsKey(EXTRA_QUERY)) {
|
||||
query = extras.getString(EXTRA_QUERY);
|
||||
} else if (extras.containsKey(EXTRA_KEY_ID)) {
|
||||
long keyId = extras.getLong(EXTRA_KEY_ID, 0);
|
||||
if (keyId != 0) {
|
||||
query = KeyFormattingUtils.convertKeyIdToHex(keyId);
|
||||
}
|
||||
// action: directly load data
|
||||
startListFragment(savedInstanceState, importData, null, null);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ACTION_IMPORT_KEY_FROM_KEYSERVER:
|
||||
case ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE:
|
||||
case ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT: {
|
||||
|
||||
if (query != null && query.length() > 0) {
|
||||
// display keyserver fragment with query
|
||||
startCloudFragment(savedInstanceState, query, false);
|
||||
// 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 */
|
||||
|
||||
// action: search immediately
|
||||
startListFragment(savedInstanceState, null, null, query);
|
||||
String query = null;
|
||||
if (extras.containsKey(EXTRA_QUERY)) {
|
||||
query = extras.getString(EXTRA_QUERY);
|
||||
} else if (extras.containsKey(EXTRA_KEY_ID)) {
|
||||
long keyId = extras.getLong(EXTRA_KEY_ID, 0);
|
||||
if (keyId != 0) {
|
||||
query = KeyFormattingUtils.convertKeyIdToHex(keyId);
|
||||
}
|
||||
}
|
||||
|
||||
if (query != null && query.length() > 0) {
|
||||
// display keyserver fragment with query
|
||||
startCloudFragment(savedInstanceState, query, false);
|
||||
|
||||
// action: search immediately
|
||||
startListFragment(savedInstanceState, null, null, query);
|
||||
} else {
|
||||
Log.e(Constants.TAG, "Query is empty!");
|
||||
return;
|
||||
}
|
||||
} else if (extras.containsKey(EXTRA_FINGERPRINT)) {
|
||||
/*
|
||||
* search based on fingerprint, here we can enforce a check in the end
|
||||
* if the right key has been downloaded
|
||||
*/
|
||||
|
||||
String fingerprint = extras.getString(EXTRA_FINGERPRINT);
|
||||
if (isFingerprintValid(fingerprint)) {
|
||||
String query = "0x" + fingerprint;
|
||||
|
||||
// display keyserver fragment with query
|
||||
startCloudFragment(savedInstanceState, query, true);
|
||||
|
||||
// action: search immediately
|
||||
startListFragment(savedInstanceState, null, null, query);
|
||||
}
|
||||
} else {
|
||||
Log.e(Constants.TAG, "Query is empty!");
|
||||
Log.e(Constants.TAG,
|
||||
"IMPORT_KEY_FROM_KEYSERVER action needs to contain the 'query', 'key_id', or " +
|
||||
"'fingerprint' extra!"
|
||||
);
|
||||
return;
|
||||
}
|
||||
} else if (extras.containsKey(EXTRA_FINGERPRINT)) {
|
||||
/*
|
||||
* search based on fingerprint, here we can enforce a check in the end
|
||||
* if the right key has been downloaded
|
||||
*/
|
||||
|
||||
String fingerprint = extras.getString(EXTRA_FINGERPRINT);
|
||||
if (isFingerprintValid(fingerprint)) {
|
||||
String query = "0x" + fingerprint;
|
||||
|
||||
// display keyserver fragment with query
|
||||
startCloudFragment(savedInstanceState, query, true);
|
||||
|
||||
// action: search immediately
|
||||
startListFragment(savedInstanceState, null, null, query);
|
||||
}
|
||||
} else {
|
||||
Log.e(Constants.TAG,
|
||||
"IMPORT_KEY_FROM_KEYSERVER action needs to contain the 'query', 'key_id', or " +
|
||||
"'fingerprint' extra!"
|
||||
);
|
||||
return;
|
||||
break;
|
||||
}
|
||||
} else if (ACTION_IMPORT_KEY_FROM_FILE.equals(action)) {
|
||||
// NOTE: this only displays the appropriate fragment, no actions are taken
|
||||
startFileFragment(savedInstanceState);
|
||||
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);
|
||||
} else if (ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN.equals(action)) {
|
||||
// NOTE: this only displays the appropriate fragment, no actions are taken
|
||||
startFileFragment(savedInstanceState);
|
||||
// no immediate actions!
|
||||
startListFragment(savedInstanceState, null, null, null);
|
||||
break;
|
||||
}
|
||||
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);
|
||||
} else if (ACTION_IMPORT_KEY_FROM_NFC.equals(action)) {
|
||||
// NOTE: this only displays the appropriate fragment, no actions are taken
|
||||
startFileFragment(savedInstanceState);
|
||||
// TODO!!!!!
|
||||
|
||||
// no immediate actions!
|
||||
startListFragment(savedInstanceState, null, null, null);
|
||||
} else {
|
||||
startCloudFragment(savedInstanceState, null, false);
|
||||
startListFragment(savedInstanceState, null, null, null);
|
||||
// no immediate actions!
|
||||
startListFragment(savedInstanceState, null, null, null);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
startCloudFragment(savedInstanceState, null, false);
|
||||
startListFragment(savedInstanceState, null, null, null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void startListFragment(Bundle savedInstanceState, byte[] bytes, Uri dataUri, String serverQuery) {
|
||||
// However, if we're being restored from a previous state,
|
||||
// then we don't need to do anything and should return or else
|
||||
@@ -353,7 +356,7 @@ public class ImportKeysActivity extends ActionBarActivity {
|
||||
// We parcel this iteratively into a file - anything we can
|
||||
// display here, we should be able to import.
|
||||
ParcelableFileCache<ParcelableKeyRing> cache =
|
||||
new ParcelableFileCache<ParcelableKeyRing>(this, "key_import.pcl");
|
||||
new ParcelableFileCache<>(this, "key_import.pcl");
|
||||
cache.writeCache(selectedEntries);
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
@@ -385,7 +388,7 @@ public class ImportKeysActivity extends ActionBarActivity {
|
||||
data.putString(KeychainIntentService.IMPORT_KEY_SERVER, sls.mCloudPrefs.keyserver);
|
||||
|
||||
// get selected key entries
|
||||
ArrayList<ParcelableKeyRing> keys = new ArrayList<ParcelableKeyRing>();
|
||||
ArrayList<ParcelableKeyRing> keys = new ArrayList<>();
|
||||
{
|
||||
// change the format into ParcelableKeyRing
|
||||
ArrayList<ImportKeysListEntry> entries = mListFragment.getSelectedEntries();
|
||||
@@ -413,50 +416,4 @@ public class ImportKeysActivity extends ActionBarActivity {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* NFC
|
||||
*/
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
// Check to see if the Activity started due to an Android Beam
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
|
||||
handleActionNdefDiscovered(getIntent());
|
||||
} else {
|
||||
Log.d(Constants.TAG, "NFC: No NDEF discovered!");
|
||||
}
|
||||
} else {
|
||||
Log.e(Constants.TAG, "Android Beam not supported by Android < 4.1");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* NFC
|
||||
*/
|
||||
@Override
|
||||
public void onNewIntent(Intent intent) {
|
||||
// onResume gets called after this to handle the intent
|
||||
setIntent(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* NFC: Parses the NDEF Message from the intent and prints to the TextView
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
||||
void handleActionNdefDiscovered(Intent intent) {
|
||||
Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
|
||||
// only one message sent during the beam
|
||||
NdefMessage msg = (NdefMessage) rawMsgs[0];
|
||||
// record 0 contains the MIME type, record 1 is the AAR, if present
|
||||
byte[] receivedKeyringBytes = msg.getRecords()[0].getPayload();
|
||||
|
||||
Intent importIntent = new Intent(this, ImportKeysActivity.class);
|
||||
importIntent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY);
|
||||
importIntent.putExtra(ImportKeysActivity.EXTRA_KEY_BYTES, receivedKeyringBytes);
|
||||
|
||||
handleActions(null, importIntent);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+6
-7
@@ -21,6 +21,7 @@ import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceActivity;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -36,8 +37,8 @@ import android.widget.TextView;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.util.ContactHelper;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -81,7 +82,7 @@ public class ImportKeysCloudFragment extends Fragment {
|
||||
namesAndEmails.addAll(ContactHelper.getContactMails(getActivity()));
|
||||
mQueryEditText.setThreshold(3);
|
||||
mQueryEditText.setAdapter(
|
||||
new ArrayAdapter<String>
|
||||
new ArrayAdapter<>
|
||||
(getActivity(), android.R.layout.simple_spinner_dropdown_item,
|
||||
namesAndEmails
|
||||
)
|
||||
@@ -110,11 +111,9 @@ public class ImportKeysCloudFragment extends Fragment {
|
||||
mConfigButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent i = new Intent(mImportActivity, PreferencesActivity.class);
|
||||
// GRR, for some reason I can’t set the Action or I get an incomprehensible
|
||||
// exception about “modern two-pane layouts”
|
||||
// i.setAction(PreferencesActivity.ACTION_PREFS_CLOUD);
|
||||
startActivity(i);
|
||||
Intent intent = new Intent(mImportActivity, SettingsActivity.class);
|
||||
intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, SettingsActivity.CloudSearchPrefsFragment.class.getName());
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+1
-1
@@ -113,7 +113,7 @@ public class ImportKeysListFragment extends ListFragment implements
|
||||
return mAdapter.getSelectedEntries();
|
||||
} else {
|
||||
Log.e(Constants.TAG, "Adapter not initialized, returning empty list");
|
||||
return new ArrayList<ImportKeysListEntry>();
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+121
-40
@@ -17,12 +17,17 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.nfc.NdefMessage;
|
||||
import android.nfc.NfcAdapter;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.Parcelable;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.widget.Toast;
|
||||
|
||||
@@ -48,10 +53,13 @@ import java.util.Locale;
|
||||
/**
|
||||
* Proxy activity (just a transparent content view) to scan QR Codes using the Barcode Scanner app
|
||||
*/
|
||||
public class QrCodeScanActivity extends FragmentActivity {
|
||||
public class ImportKeysProxyActivity extends FragmentActivity {
|
||||
|
||||
public static final String ACTION_QR_CODE_API = OpenKeychainIntents.IMPORT_KEY_FROM_QR_CODE;
|
||||
public static final String ACTION_SCAN_WITH_RESULT = Constants.INTENT_PREFIX + "SCAN_QR_CODE_WITH_RESULT";
|
||||
public static final String ACTION_SCAN_IMPORT = Constants.INTENT_PREFIX + "SCAN_QR_CODE_IMPORT";
|
||||
|
||||
public static final String EXTRA_FINGERPRINT = "fingerprint";
|
||||
|
||||
boolean returnResult;
|
||||
|
||||
@@ -73,17 +81,35 @@ public class QrCodeScanActivity extends FragmentActivity {
|
||||
// Scanning a fingerprint directly with Barcode Scanner, thus we already have scanned
|
||||
|
||||
returnResult = false;
|
||||
startCertify(dataUri);
|
||||
processScannedContent(dataUri);
|
||||
} else if (ACTION_SCAN_IMPORT.equals(action)) {
|
||||
returnResult = false;
|
||||
IntentIntegrator integrator = new IntentIntegrator(this);
|
||||
integrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE_TYPES)
|
||||
.setPrompt(getString(R.string.import_qr_code_text))
|
||||
.setResultDisplayDuration(0)
|
||||
.initiateScan();
|
||||
} else if (ACTION_SCAN_WITH_RESULT.equals(action)) {
|
||||
// scan using xzing's Barcode Scanner and return result parcel in OpenKeychain
|
||||
|
||||
returnResult = true;
|
||||
new IntentIntegrator(this).initiateScan();
|
||||
IntentIntegrator integrator = new IntentIntegrator(this);
|
||||
integrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE_TYPES)
|
||||
.setPrompt(getString(R.string.import_qr_code_text))
|
||||
.setResultDisplayDuration(0)
|
||||
.initiateScan();
|
||||
} else if (ACTION_QR_CODE_API.equals(action)) {
|
||||
// scan using xzing's Barcode Scanner from outside OpenKeychain
|
||||
|
||||
returnResult = false;
|
||||
new IntentIntegrator(this).initiateScan();
|
||||
} 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) {
|
||||
returnResult = false;
|
||||
handleActionNdefDiscovered(getIntent());
|
||||
} else {
|
||||
Log.e(Constants.TAG, "Android Beam not supported by Android < 4.1");
|
||||
finish();
|
||||
}
|
||||
} else {
|
||||
Log.e(Constants.TAG, "No valid scheme or action given!");
|
||||
finish();
|
||||
@@ -95,16 +121,16 @@ public class QrCodeScanActivity extends FragmentActivity {
|
||||
if (requestCode == IntentIntegratorSupportV4.REQUEST_CODE) {
|
||||
IntentResult scanResult = IntentIntegratorSupportV4.parseActivityResult(requestCode,
|
||||
resultCode, data);
|
||||
if (scanResult != null && scanResult.getFormatName() != null) {
|
||||
String scannedContent = scanResult.getContents();
|
||||
Log.d(Constants.TAG, "scannedContent: " + scannedContent);
|
||||
|
||||
startCertify(Uri.parse(scanResult.getContents()));
|
||||
} else {
|
||||
if (scanResult == null || scanResult.getFormatName() == null) {
|
||||
Log.e(Constants.TAG, "scanResult or formatName null! Should not happen!");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
String scannedContent = scanResult.getContents();
|
||||
processScannedContent(scannedContent);
|
||||
|
||||
return;
|
||||
}
|
||||
// if a result has been returned, return it down to other activity
|
||||
@@ -112,6 +138,42 @@ public class QrCodeScanActivity extends FragmentActivity {
|
||||
returnResult(data);
|
||||
} else {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private void processScannedContent(String content) {
|
||||
Uri uri = Uri.parse(content);
|
||||
processScannedContent(uri);
|
||||
}
|
||||
|
||||
private void processScannedContent(Uri uri) {
|
||||
|
||||
Log.d(Constants.TAG, "scanned: " + uri);
|
||||
|
||||
String fingerprint = null;
|
||||
|
||||
// example: openpgp4fpr:73EE2314F65FA92EC2390D3A718C070100012282
|
||||
if (uri.getScheme().toLowerCase(Locale.ENGLISH).equals(Constants.FINGERPRINT_SCHEME)) {
|
||||
fingerprint = uri.getEncodedSchemeSpecificPart().toLowerCase(Locale.ENGLISH);
|
||||
}
|
||||
|
||||
if (fingerprint == null) {
|
||||
SingletonResult result = new SingletonResult(
|
||||
SingletonResult.RESULT_ERROR, OperationResult.LogType.MSG_WRONG_QR_CODE);
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(SingletonResult.EXTRA_RESULT, result);
|
||||
returnResult(intent);
|
||||
return;
|
||||
}
|
||||
|
||||
if (returnResult) {
|
||||
Intent result = new Intent();
|
||||
result.putExtra(EXTRA_FINGERPRINT, fingerprint);
|
||||
setResult(RESULT_OK, result);
|
||||
finish();
|
||||
} else {
|
||||
importKeys(fingerprint);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,21 +190,28 @@ public class QrCodeScanActivity extends FragmentActivity {
|
||||
}
|
||||
}
|
||||
|
||||
public void startCertify(Uri dataUri) {
|
||||
// example: openpgp4fpr:73EE2314F65FA92EC2390D3A718C070100012282
|
||||
if (dataUri.getScheme().equals(Constants.FINGERPRINT_SCHEME)) {
|
||||
String fingerprint = dataUri.getEncodedSchemeSpecificPart().toLowerCase(Locale.ENGLISH);
|
||||
importKeys(fingerprint);
|
||||
} else {
|
||||
SingletonResult result = new SingletonResult(
|
||||
SingletonResult.RESULT_ERROR, OperationResult.LogType.MSG_WRONG_QR_CODE);
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(SingletonResult.EXTRA_RESULT, result);
|
||||
returnResult(intent);
|
||||
}
|
||||
public void importKeys(byte[] keyringData) {
|
||||
|
||||
ParcelableKeyRing keyEntry = new ParcelableKeyRing(keyringData);
|
||||
ArrayList<ParcelableKeyRing> selectedEntries = new ArrayList<>();
|
||||
selectedEntries.add(keyEntry);
|
||||
|
||||
startImportService(selectedEntries);
|
||||
|
||||
}
|
||||
|
||||
public void importKeys(String fingerprint) {
|
||||
|
||||
ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null);
|
||||
ArrayList<ParcelableKeyRing> selectedEntries = new ArrayList<>();
|
||||
selectedEntries.add(keyEntry);
|
||||
|
||||
startImportService(selectedEntries);
|
||||
|
||||
}
|
||||
|
||||
private void startImportService (ArrayList<ParcelableKeyRing> keyRings) {
|
||||
|
||||
// Message is received after importing is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler serviceHandler = new KeychainIntentServiceHandler(
|
||||
this,
|
||||
@@ -168,7 +237,7 @@ public class QrCodeScanActivity extends FragmentActivity {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! result.success()) {
|
||||
if (!result.success()) {
|
||||
// only return if no success...
|
||||
Intent data = new Intent();
|
||||
data.putExtras(returnData);
|
||||
@@ -176,34 +245,32 @@ public class QrCodeScanActivity extends FragmentActivity {
|
||||
return;
|
||||
}
|
||||
|
||||
Intent certifyIntent = new Intent(QrCodeScanActivity.this, CertifyKeyActivity.class);
|
||||
Intent certifyIntent = new Intent(ImportKeysProxyActivity.this,
|
||||
CertifyKeyActivity.class);
|
||||
certifyIntent.putExtra(CertifyKeyActivity.EXTRA_RESULT, result);
|
||||
certifyIntent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, result.getImportedMasterKeyIds());
|
||||
certifyIntent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS,
|
||||
result.getImportedMasterKeyIds());
|
||||
startActivityForResult(certifyIntent, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// search config
|
||||
Preferences prefs = Preferences.getPreferences(this);
|
||||
Preferences.CloudSearchPrefs cloudPrefs = new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver());
|
||||
|
||||
// Send all information needed to service to query keys in other thread
|
||||
Intent intent = new Intent(this, KeychainIntentService.class);
|
||||
|
||||
intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
data.putString(KeychainIntentService.IMPORT_KEY_SERVER, cloudPrefs.keyserver);
|
||||
// search config
|
||||
{
|
||||
Preferences prefs = Preferences.getPreferences(this);
|
||||
Preferences.CloudSearchPrefs cloudPrefs =
|
||||
new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver());
|
||||
data.putString(KeychainIntentService.IMPORT_KEY_SERVER, cloudPrefs.keyserver);
|
||||
}
|
||||
|
||||
ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null);
|
||||
ArrayList<ParcelableKeyRing> selectedEntries = new ArrayList<ParcelableKeyRing>();
|
||||
selectedEntries.add(keyEntry);
|
||||
|
||||
data.putParcelableArrayList(KeychainIntentService.IMPORT_KEY_LIST, selectedEntries);
|
||||
data.putParcelableArrayList(KeychainIntentService.IMPORT_KEY_LIST, keyRings);
|
||||
|
||||
// Send all information needed to service to query keys in other thread
|
||||
Intent intent = new Intent(this, KeychainIntentService.class);
|
||||
intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYRING);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
@@ -215,6 +282,20 @@ public class QrCodeScanActivity extends FragmentActivity {
|
||||
|
||||
// start service with intent
|
||||
startService(intent);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* NFC: Parses the NDEF Message from the intent and prints to the TextView
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
||||
void handleActionNdefDiscovered(Intent intent) {
|
||||
Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
|
||||
// only one message sent during the beam
|
||||
NdefMessage msg = (NdefMessage) rawMsgs[0];
|
||||
// record 0 contains the MIME type, record 1 is the AAR, if present
|
||||
byte[] receivedKeyringBytes = msg.getRecords()[0].getPayload();
|
||||
importKeys(receivedKeyringBytes);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,229 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.ConsolidateResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.util.ExportHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class KeyListActivity extends DrawerActivity {
|
||||
|
||||
public static final int REQUEST_CODE_RESULT_TO_LIST = 1;
|
||||
|
||||
ExportHelper mExportHelper;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setTitle(R.string.nav_keys);
|
||||
|
||||
// if this is the first time show first time activity
|
||||
Preferences prefs = Preferences.getPreferences(this);
|
||||
if (prefs.isFirstTime()) {
|
||||
startActivity(new Intent(this, FirstTimeActivity.class));
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
mExportHelper = new ExportHelper(this);
|
||||
|
||||
setContentView(R.layout.key_list_activity);
|
||||
|
||||
Intent data = getIntent();
|
||||
// If we got an EXTRA_RESULT in the intent, show the notification
|
||||
if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) {
|
||||
OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT);
|
||||
result.createNotify(this).show();
|
||||
}
|
||||
|
||||
// now setup navigation drawer in DrawerActivity...
|
||||
activateDrawerNavigation(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
getMenuInflater().inflate(R.menu.key_list, menu);
|
||||
|
||||
if (Constants.DEBUG) {
|
||||
menu.findItem(R.id.menu_key_list_debug_cons).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);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_key_list_add:
|
||||
Intent scanQrCode = new Intent(this, QrCodeScanActivity.class);
|
||||
scanQrCode.setAction(QrCodeScanActivity.ACTION_SCAN_WITH_RESULT);
|
||||
startActivityForResult(scanQrCode, 0);
|
||||
return true;
|
||||
|
||||
case R.id.menu_key_list_search_cloud:
|
||||
searchCloud();
|
||||
return true;
|
||||
|
||||
case R.id.menu_key_list_create:
|
||||
createKey();
|
||||
return true;
|
||||
|
||||
case R.id.menu_key_list_import_existing_key:
|
||||
Intent intentImportExisting = new Intent(this, ImportKeysActivity.class);
|
||||
intentImportExisting.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN);
|
||||
startActivityForResult(intentImportExisting, 0);
|
||||
return true;
|
||||
|
||||
case R.id.menu_key_list_export:
|
||||
mExportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, true);
|
||||
return true;
|
||||
|
||||
case R.id.menu_key_list_debug_cons:
|
||||
consolidate();
|
||||
return true;
|
||||
|
||||
case R.id.menu_key_list_debug_read:
|
||||
try {
|
||||
KeychainDatabase.debugBackup(this, true);
|
||||
Notify.showNotify(this, "Restored debug_backup.db", Notify.Style.INFO);
|
||||
getContentResolver().notifyChange(KeychainContract.KeyRings.CONTENT_URI, null);
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "IO Error", e);
|
||||
Notify.showNotify(this, "IO Error " + e.getMessage(), Notify.Style.ERROR);
|
||||
}
|
||||
return true;
|
||||
|
||||
case R.id.menu_key_list_debug_write:
|
||||
try {
|
||||
KeychainDatabase.debugBackup(this, false);
|
||||
Notify.showNotify(this, "Backup to debug_backup.db completed", Notify.Style.INFO);
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "IO Error", e);
|
||||
Notify.showNotify(this, "IO Error: " + e.getMessage(), Notify.Style.ERROR);
|
||||
}
|
||||
return true;
|
||||
|
||||
case R.id.menu_key_list_debug_first_time:
|
||||
Preferences prefs = Preferences.getPreferences(this);
|
||||
prefs.setFirstTime(true);
|
||||
Intent intent = new Intent(this, FirstTimeActivity.class);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
return true;
|
||||
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void searchCloud() {
|
||||
Intent importIntent = new Intent(this, ImportKeysActivity.class);
|
||||
importIntent.putExtra(ImportKeysActivity.EXTRA_QUERY, (String) null); // hack to show only cloud tab
|
||||
startActivity(importIntent);
|
||||
}
|
||||
|
||||
private void createKey() {
|
||||
Intent intent = new Intent(this, CreateKeyActivity.class);
|
||||
startActivityForResult(intent, 0);
|
||||
}
|
||||
|
||||
private void consolidate() {
|
||||
// Message is received after importing is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
|
||||
this,
|
||||
getString(R.string.progress_importing),
|
||||
ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
// get returned data bundle
|
||||
Bundle returnData = message.getData();
|
||||
if (returnData == null) {
|
||||
return;
|
||||
}
|
||||
final ConsolidateResult result =
|
||||
returnData.getParcelable(OperationResult.EXTRA_RESULT);
|
||||
if (result == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
result.createNotify(KeyListActivity.this).show();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Send all information needed to service to import key in other thread
|
||||
Intent intent = new Intent(this, KeychainIntentService.class);
|
||||
|
||||
intent.setAction(KeychainIntentService.ACTION_CONSOLIDATE);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
saveHandler.showProgressDialog(this);
|
||||
|
||||
// start service with intent
|
||||
startService(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
// if a result has been returned, display a notify
|
||||
if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) {
|
||||
OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT);
|
||||
result.createNotify(this).show();
|
||||
} else {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+278
-186
@@ -18,6 +18,7 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
@@ -31,62 +32,54 @@ import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
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.NoScrollableSwipeRefreshLayout;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.support.v7.widget.SearchView;
|
||||
import android.view.ActionMode;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
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.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
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.ConsolidateResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.DeleteResult;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.ExportHelper;
|
||||
import org.sufficientlysecure.keychain.util.KeyUpdateHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.widget.ListAwareSwipeRefreshLayout;
|
||||
import org.sufficientlysecure.keychain.ui.util.Highlighter;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.util.ParcelableFileCache;
|
||||
import org.sufficientlysecure.keychain.util.ExportHelper;
|
||||
import org.sufficientlysecure.keychain.util.FabContainer;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
|
||||
import edu.cmu.cylab.starslinger.exchange.ExchangeActivity;
|
||||
import edu.cmu.cylab.starslinger.exchange.ExchangeConfig;
|
||||
import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter;
|
||||
import se.emilsjolander.stickylistheaders.StickyListHeadersListView;
|
||||
|
||||
@@ -96,21 +89,26 @@ import se.emilsjolander.stickylistheaders.StickyListHeadersListView;
|
||||
*/
|
||||
public class KeyListFragment extends LoaderFragment
|
||||
implements SearchView.OnQueryTextListener, AdapterView.OnItemClickListener,
|
||||
LoaderManager.LoaderCallbacks<Cursor> {
|
||||
LoaderManager.LoaderCallbacks<Cursor>, FabContainer {
|
||||
|
||||
ExportHelper mExportHelper;
|
||||
|
||||
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;
|
||||
|
||||
private boolean mShowAllKeys = true;
|
||||
|
||||
private String mQuery;
|
||||
private SearchView mSearchView;
|
||||
|
||||
boolean hideMenu = false;
|
||||
private FloatingActionsMenu mFab;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mExportHelper = new ExportHelper(getActivity());
|
||||
}
|
||||
|
||||
/**
|
||||
* Load custom layout with StickyListView from library
|
||||
@@ -123,73 +121,38 @@ public class KeyListFragment extends LoaderFragment
|
||||
mStickyList = (StickyListHeadersListView) view.findViewById(R.id.key_list_list);
|
||||
mStickyList.setOnItemClickListener(this);
|
||||
|
||||
mSwipeRefreshLayout = (ListAwareSwipeRefreshLayout) view.findViewById(R.id.key_list_swipe_container);
|
||||
mSwipeRefreshLayout.setOnRefreshListener(new NoScrollableSwipeRefreshLayout.OnRefreshListener() {
|
||||
mFab = (FloatingActionsMenu) view.findViewById(R.id.fab_main);
|
||||
|
||||
FloatingActionButton fabQrCode = (FloatingActionButton) view.findViewById(R.id.fab_add_qr_code);
|
||||
FloatingActionButton fabCloud = (FloatingActionButton) view.findViewById(R.id.fab_add_cloud);
|
||||
FloatingActionButton fabFile = (FloatingActionButton) view.findViewById(R.id.fab_add_file);
|
||||
|
||||
fabQrCode.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onRefresh() {
|
||||
KeychainIntentServiceHandler finishedHandler = new KeychainIntentServiceHandler(getActivity()) {
|
||||
public void handleMessage(Message message) {
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
// new KeyUpdateHelper().updateAllKeys(getActivity(), finishedHandler);
|
||||
updateActionbarForSwipe(false);
|
||||
public void onClick(View v) {
|
||||
mFab.collapse();
|
||||
scanQrCode();
|
||||
}
|
||||
});
|
||||
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);
|
||||
mSwipeRefreshLayout.setOnTouchListener(new View.OnTouchListener() {
|
||||
fabCloud.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
if (event.getAction() == MotionEvent.ACTION_MOVE) {
|
||||
updateActionbarForSwipe(true);
|
||||
} else {
|
||||
updateActionbarForSwipe(false);
|
||||
}
|
||||
return false;
|
||||
public void onClick(View v) {
|
||||
mFab.collapse();
|
||||
searchCloud();
|
||||
}
|
||||
});
|
||||
// Just disable for now
|
||||
mSwipeRefreshLayout.setIsLocked(true);
|
||||
fabFile.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
mFab.collapse();
|
||||
importFile();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private void updateActionbarForSwipe(boolean show) {
|
||||
ActionBarActivity activity = (ActionBarActivity) getActivity();
|
||||
ActionBar bar = activity.getSupportActionBar();
|
||||
|
||||
if (show) {
|
||||
bar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
|
||||
bar.setDisplayUseLogoEnabled(false);
|
||||
bar.setCustomView(R.layout.custom_actionbar);
|
||||
TextView title = (TextView) getActivity().findViewById(R.id.custom_actionbar_text);
|
||||
title.setText(R.string.swipe_to_update);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
hideMenu = true;
|
||||
activity.invalidateOptionsMenu();
|
||||
}
|
||||
} else {
|
||||
bar.setTitle(getActivity().getTitle());
|
||||
bar.setDisplayHomeAsUpEnabled(true);
|
||||
bar.setDisplayShowTitleEnabled(true);
|
||||
bar.setDisplayUseLogoEnabled(true);
|
||||
bar.setDisplayShowHomeEnabled(true);
|
||||
bar.setDisplayShowCustomEnabled(false);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
hideMenu = false;
|
||||
activity.invalidateOptionsMenu();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Define Adapter and Loader on create of Activity
|
||||
*/
|
||||
@@ -198,90 +161,91 @@ public class KeyListFragment extends LoaderFragment
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
// show app name instead of "keys" from nav drawer
|
||||
getActivity().setTitle(R.string.app_name);
|
||||
|
||||
mStickyList.setOnItemClickListener(this);
|
||||
mStickyList.setAreHeadersSticky(true);
|
||||
mStickyList.setDrawingListUnderStickyHeader(false);
|
||||
mStickyList.setFastScrollEnabled(true);
|
||||
|
||||
/*
|
||||
* Multi-selection is only available for Android >= 3.0
|
||||
* Multi-selection
|
||||
*/
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
mStickyList.setFastScrollAlwaysVisible(true);
|
||||
mStickyList.setFastScrollAlwaysVisible(true);
|
||||
|
||||
mStickyList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
|
||||
mStickyList.getWrappedList().setMultiChoiceModeListener(new MultiChoiceModeListener() {
|
||||
mStickyList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
|
||||
mStickyList.getWrappedList().setMultiChoiceModeListener(new MultiChoiceModeListener() {
|
||||
|
||||
@Override
|
||||
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
||||
android.view.MenuInflater inflater = getActivity().getMenuInflater();
|
||||
inflater.inflate(R.menu.key_list_multi, menu);
|
||||
mActionMode = mode;
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
||||
android.view.MenuInflater inflater = getActivity().getMenuInflater();
|
||||
inflater.inflate(R.menu.key_list_multi, menu);
|
||||
mActionMode = mode;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
||||
@Override
|
||||
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
||||
|
||||
// get IDs for checked positions as long array
|
||||
long[] ids;
|
||||
// get IDs for checked positions as long array
|
||||
long[] ids;
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_key_list_multi_encrypt: {
|
||||
ids = mAdapter.getCurrentSelectedMasterKeyIds();
|
||||
encrypt(mode, ids);
|
||||
break;
|
||||
}
|
||||
case R.id.menu_key_list_multi_delete: {
|
||||
ids = mAdapter.getCurrentSelectedMasterKeyIds();
|
||||
showDeleteKeyDialog(mode, ids, mAdapter.isAnySecretSelected());
|
||||
break;
|
||||
}
|
||||
case R.id.menu_key_list_multi_export: {
|
||||
ids = mAdapter.getCurrentSelectedMasterKeyIds();
|
||||
ExportHelper mExportHelper = new ExportHelper((ActionBarActivity) getActivity());
|
||||
mExportHelper.showExportKeysDialog(ids, Constants.Path.APP_DIR_FILE,
|
||||
mAdapter.isAnySecretSelected());
|
||||
break;
|
||||
}
|
||||
case R.id.menu_key_list_multi_select_all: {
|
||||
// select all
|
||||
for (int i = 0; i < mStickyList.getCount(); i++) {
|
||||
mStickyList.setItemChecked(i, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_key_list_multi_encrypt: {
|
||||
ids = mAdapter.getCurrentSelectedMasterKeyIds();
|
||||
encrypt(mode, ids);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyActionMode(ActionMode mode) {
|
||||
mActionMode = null;
|
||||
mAdapter.clearSelection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
|
||||
boolean checked) {
|
||||
if (checked) {
|
||||
mAdapter.setNewSelection(position, checked);
|
||||
} else {
|
||||
mAdapter.removeSelection(position);
|
||||
case R.id.menu_key_list_multi_delete: {
|
||||
ids = mAdapter.getCurrentSelectedMasterKeyIds();
|
||||
showDeleteKeyDialog(mode, ids, mAdapter.isAnySecretSelected());
|
||||
break;
|
||||
}
|
||||
case R.id.menu_key_list_multi_export: {
|
||||
ids = mAdapter.getCurrentSelectedMasterKeyIds();
|
||||
ExportHelper mExportHelper = new ExportHelper(getActivity());
|
||||
mExportHelper.showExportKeysDialog(ids, Constants.Path.APP_DIR_FILE,
|
||||
mAdapter.isAnySecretSelected());
|
||||
break;
|
||||
}
|
||||
case R.id.menu_key_list_multi_select_all: {
|
||||
// select all
|
||||
for (int i = 0; i < mStickyList.getCount(); i++) {
|
||||
mStickyList.setItemChecked(i, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
int count = mStickyList.getCheckedItemCount();
|
||||
String keysSelected = getResources().getQuantityString(
|
||||
R.plurals.key_list_selected_keys, count, count);
|
||||
mode.setTitle(keysSelected);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
@Override
|
||||
public void onDestroyActionMode(ActionMode mode) {
|
||||
mActionMode = null;
|
||||
mAdapter.clearSelection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
|
||||
boolean checked) {
|
||||
if (checked) {
|
||||
mAdapter.setNewSelection(position, true);
|
||||
} else {
|
||||
mAdapter.removeSelection(position);
|
||||
}
|
||||
int count = mStickyList.getCheckedItemCount();
|
||||
String keysSelected = getResources().getQuantityString(
|
||||
R.plurals.key_list_selected_keys, count, count);
|
||||
mode.setTitle(keysSelected);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// We have a menu item to show in action bar.
|
||||
setHasOptionsMenu(true);
|
||||
@@ -340,14 +304,6 @@ public class KeyListFragment extends LoaderFragment
|
||||
whereArgs[i] = "%" + words[i] + "%";
|
||||
}
|
||||
}
|
||||
if (!mShowAllKeys) {
|
||||
if (where == null) {
|
||||
where = "";
|
||||
} else {
|
||||
where += " AND ";
|
||||
}
|
||||
where += KeyRings.VERIFIED + " != 0";
|
||||
}
|
||||
|
||||
// Now create and return a CursorLoader that will take care of
|
||||
// creating a Cursor for the data being displayed.
|
||||
@@ -368,9 +324,7 @@ public class KeyListFragment extends LoaderFragment
|
||||
|
||||
// end action mode, if any
|
||||
if (mActionMode != null) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
mActionMode.finish();
|
||||
}
|
||||
mActionMode.finish();
|
||||
}
|
||||
|
||||
// The list should now be shown.
|
||||
@@ -400,7 +354,6 @@ public class KeyListFragment extends LoaderFragment
|
||||
startActivity(viewIntent);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
protected void encrypt(ActionMode mode, long[] masterKeyIds) {
|
||||
Intent intent = new Intent(getActivity(), EncryptFilesActivity.class);
|
||||
intent.setAction(EncryptFilesActivity.ACTION_ENCRYPT_DATA);
|
||||
@@ -414,10 +367,8 @@ public class KeyListFragment extends LoaderFragment
|
||||
/**
|
||||
* Show dialog to delete key
|
||||
*
|
||||
* @param masterKeyIds
|
||||
* @param hasSecret must contain whether the list of masterKeyIds contains a secret key or not
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
public void showDeleteKeyDialog(final ActionMode mode, long[] masterKeyIds, boolean hasSecret) {
|
||||
// Can only work on singular secret keys
|
||||
if (hasSecret && masterKeyIds.length > 1) {
|
||||
@@ -455,25 +406,27 @@ public class KeyListFragment extends LoaderFragment
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.key_list, menu);
|
||||
|
||||
if (Constants.DEBUG) {
|
||||
menu.findItem(R.id.menu_key_list_debug_cons).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);
|
||||
}
|
||||
|
||||
// Get the searchview
|
||||
MenuItem searchItem = menu.findItem(R.id.menu_key_list_search);
|
||||
|
||||
mSearchView = (SearchView) MenuItemCompat.getActionView(searchItem);
|
||||
SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
|
||||
|
||||
// Execute this when searching
|
||||
mSearchView.setOnQueryTextListener(this);
|
||||
|
||||
View searchPlate = mSearchView.findViewById(android.support.v7.appcompat.R.id.search_plate);
|
||||
searchPlate.setBackgroundResource(R.drawable.keychaintheme_searchview_holo_light);
|
||||
searchView.setOnQueryTextListener(this);
|
||||
|
||||
// Erase search result without focus
|
||||
MenuItemCompat.setOnActionExpandListener(searchItem, new MenuItemCompat.OnActionExpandListener() {
|
||||
@Override
|
||||
public boolean onMenuItemActionExpand(MenuItem item) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
hideMenu = true;
|
||||
getActivity().invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
// disable swipe-to-refresh
|
||||
// mSwipeRefreshLayout.setIsLocked(true);
|
||||
@@ -485,25 +438,65 @@ public class KeyListFragment extends LoaderFragment
|
||||
mQuery = null;
|
||||
getLoaderManager().restartLoader(0, null, KeyListFragment.this);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
hideMenu = false;
|
||||
getActivity().invalidateOptionsMenu();
|
||||
}
|
||||
// enable swipe-to-refresh
|
||||
// mSwipeRefreshLayout.setIsLocked(false);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
if (hideMenu) {
|
||||
for (int i = 0; i < menu.size(); i++) {
|
||||
menu.getItem(i).setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
|
||||
case R.id.menu_key_list_create:
|
||||
createKey();
|
||||
return true;
|
||||
|
||||
case R.id.menu_key_list_export:
|
||||
mExportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, true);
|
||||
return true;
|
||||
|
||||
case R.id.menu_key_list_debug_cons:
|
||||
consolidate();
|
||||
return true;
|
||||
|
||||
case R.id.menu_key_list_debug_read:
|
||||
try {
|
||||
KeychainDatabase.debugBackup(getActivity(), true);
|
||||
Notify.showNotify(getActivity(), "Restored debug_backup.db", Notify.Style.INFO);
|
||||
getActivity().getContentResolver().notifyChange(KeychainContract.KeyRings.CONTENT_URI, null);
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "IO Error", e);
|
||||
Notify.showNotify(getActivity(), "IO Error " + e.getMessage(), Notify.Style.ERROR);
|
||||
}
|
||||
return true;
|
||||
|
||||
case R.id.menu_key_list_debug_write:
|
||||
try {
|
||||
KeychainDatabase.debugBackup(getActivity(), false);
|
||||
Notify.showNotify(getActivity(), "Backup to debug_backup.db completed", Notify.Style.INFO);
|
||||
} catch (IOException e) {
|
||||
Log.e(Constants.TAG, "IO Error", e);
|
||||
Notify.showNotify(getActivity(), "IO Error: " + e.getMessage(), Notify.Style.ERROR);
|
||||
}
|
||||
return true;
|
||||
|
||||
case R.id.menu_key_list_debug_first_time:
|
||||
Preferences prefs = Preferences.getPreferences(getActivity());
|
||||
prefs.setFirstTime(true);
|
||||
Intent intent = new Intent(getActivity(), FirstTimeActivity.class);
|
||||
startActivity(intent);
|
||||
getActivity().finish();
|
||||
return true;
|
||||
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String s) {
|
||||
return true;
|
||||
@@ -526,6 +519,105 @@ public class KeyListFragment extends LoaderFragment
|
||||
return true;
|
||||
}
|
||||
|
||||
private void searchCloud() {
|
||||
Intent importIntent = new Intent(getActivity(), ImportKeysActivity.class);
|
||||
importIntent.putExtra(ImportKeysActivity.EXTRA_QUERY, (String) null); // hack to show only cloud tab
|
||||
startActivity(importIntent);
|
||||
}
|
||||
|
||||
private void scanQrCode() {
|
||||
Intent scanQrCode = new Intent(getActivity(), ImportKeysProxyActivity.class);
|
||||
scanQrCode.setAction(ImportKeysProxyActivity.ACTION_SCAN_IMPORT);
|
||||
startActivityForResult(scanQrCode, 0);
|
||||
}
|
||||
|
||||
private void importFile() {
|
||||
Intent intentImportExisting = new Intent(getActivity(), ImportKeysActivity.class);
|
||||
intentImportExisting.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN);
|
||||
startActivityForResult(intentImportExisting, 0);
|
||||
}
|
||||
|
||||
private void createKey() {
|
||||
Intent intent = new Intent(getActivity(), CreateKeyActivity.class);
|
||||
startActivityForResult(intent, 0);
|
||||
}
|
||||
|
||||
private void consolidate() {
|
||||
// Message is received after importing is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
|
||||
getActivity(),
|
||||
getString(R.string.progress_importing),
|
||||
ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
// get returned data bundle
|
||||
Bundle returnData = message.getData();
|
||||
if (returnData == null) {
|
||||
return;
|
||||
}
|
||||
final ConsolidateResult result =
|
||||
returnData.getParcelable(OperationResult.EXTRA_RESULT);
|
||||
if (result == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
result.createNotify(getActivity()).show();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Send all information needed to service to import key in other thread
|
||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
||||
|
||||
intent.setAction(KeychainIntentService.ACTION_CONSOLIDATE);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
saveHandler.showProgressDialog(getActivity());
|
||||
|
||||
// start service with intent
|
||||
getActivity().startService(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
// if a result has been returned, display a notify
|
||||
if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) {
|
||||
OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT);
|
||||
result.createNotify(getActivity()).show();
|
||||
} else {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fabMoveUp(int height) {
|
||||
ObjectAnimator anim = ObjectAnimator.ofFloat(mFab, "translationY", 0, -height);
|
||||
// we're a little behind, so skip 1/10 of the time
|
||||
anim.setDuration(270);
|
||||
anim.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fabRestorePosition() {
|
||||
ObjectAnimator anim = ObjectAnimator.ofFloat(mFab, "translationY", 0);
|
||||
// we're a little ahead, so wait a few ms
|
||||
anim.setStartDelay(70);
|
||||
anim.setDuration(300);
|
||||
anim.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements StickyListHeadersAdapter from library
|
||||
*/
|
||||
@@ -533,7 +625,7 @@ public class KeyListFragment extends LoaderFragment
|
||||
private String mQuery;
|
||||
private LayoutInflater mInflater;
|
||||
|
||||
private HashMap<Integer, Boolean> mSelection = new HashMap<Integer, Boolean>();
|
||||
private HashMap<Integer, Boolean> mSelection = new HashMap<>();
|
||||
|
||||
public KeyListAdapter(Context context, Cursor c, int flags) {
|
||||
super(context, c, flags);
|
||||
@@ -624,13 +716,13 @@ public class KeyListFragment extends LoaderFragment
|
||||
|
||||
// Note: order is important!
|
||||
if (isRevoked) {
|
||||
KeyFormattingUtils.setStatusImage(getActivity(), h.mStatus, null, KeyFormattingUtils.STATE_REVOKED, true);
|
||||
KeyFormattingUtils.setStatusImage(getActivity(), h.mStatus, null, KeyFormattingUtils.STATE_REVOKED, R.color.bg_gray);
|
||||
h.mStatus.setVisibility(View.VISIBLE);
|
||||
h.mSlinger.setVisibility(View.GONE);
|
||||
h.mMainUserId.setTextColor(context.getResources().getColor(R.color.bg_gray));
|
||||
h.mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.bg_gray));
|
||||
} else if (isExpired) {
|
||||
KeyFormattingUtils.setStatusImage(getActivity(), h.mStatus, null, KeyFormattingUtils.STATE_EXPIRED, true);
|
||||
KeyFormattingUtils.setStatusImage(getActivity(), h.mStatus, null, KeyFormattingUtils.STATE_EXPIRED, R.color.bg_gray);
|
||||
h.mStatus.setVisibility(View.VISIBLE);
|
||||
h.mSlinger.setVisibility(View.GONE);
|
||||
h.mMainUserId.setTextColor(context.getResources().getColor(R.color.bg_gray));
|
||||
|
||||
+5
-5
@@ -19,21 +19,18 @@
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.View;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.util.ActionBarHelper;
|
||||
|
||||
public class LogDisplayActivity extends ActionBarActivity {
|
||||
public class LogDisplayActivity extends BaseActivity {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Inflate a "Done" custom action bar
|
||||
ActionBarHelper.setOneButtonView(getSupportActionBar(),
|
||||
R.string.btn_okay, R.drawable.ic_action_done,
|
||||
setFullScreenDialogClose(
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
@@ -42,7 +39,10 @@ public class LogDisplayActivity extends ActionBarActivity {
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initLayout() {
|
||||
setContentView(R.layout.log_display_activity);
|
||||
}
|
||||
|
||||
|
||||
+10
-10
@@ -153,11 +153,11 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe
|
||||
switch (subEntry.mType.mLevel) {
|
||||
case DEBUG: ih.mSecondImg.setBackgroundColor(Color.GRAY); break;
|
||||
case INFO: ih.mSecondImg.setBackgroundColor(Color.BLACK); break;
|
||||
case WARN: ih.mSecondImg.setBackgroundColor(Color.YELLOW); break;
|
||||
case ERROR: ih.mSecondImg.setBackgroundColor(Color.RED); break;
|
||||
case START: ih.mSecondImg.setBackgroundColor(getResources().getColor(R.color.emphasis)); break;
|
||||
case OK: ih.mSecondImg.setBackgroundColor(Color.GREEN); break;
|
||||
case CANCELLED: ih.mSecondImg.setBackgroundColor(Color.RED); break;
|
||||
case WARN: ih.mSecondImg.setBackgroundColor(getResources().getColor(R.color.android_orange_light)); break;
|
||||
case ERROR: ih.mSecondImg.setBackgroundColor(getResources().getColor(R.color.android_red_light)); break;
|
||||
case START: ih.mSecondImg.setBackgroundColor(Color.BLACK); break;
|
||||
case OK: ih.mSecondImg.setBackgroundColor(getResources().getColor(R.color.android_green_light)); break;
|
||||
case CANCELLED: ih.mSecondImg.setBackgroundColor(getResources().getColor(R.color.android_red_light)); break;
|
||||
}
|
||||
} else {
|
||||
ih.mSecond.setVisibility(View.GONE);
|
||||
@@ -184,11 +184,11 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe
|
||||
switch (entry.mType.mLevel) {
|
||||
case DEBUG: ih.mImg.setBackgroundColor(Color.GRAY); break;
|
||||
case INFO: ih.mImg.setBackgroundColor(Color.BLACK); break;
|
||||
case WARN: ih.mImg.setBackgroundColor(Color.YELLOW); break;
|
||||
case ERROR: ih.mImg.setBackgroundColor(Color.RED); break;
|
||||
case START: ih.mImg.setBackgroundColor(getResources().getColor(R.color.emphasis)); break;
|
||||
case OK: ih.mImg.setBackgroundColor(Color.GREEN); break;
|
||||
case CANCELLED: ih.mImg.setBackgroundColor(Color.RED); break;
|
||||
case WARN: ih.mImg.setBackgroundColor(getResources().getColor(R.color.android_orange_light)); break;
|
||||
case ERROR: ih.mImg.setBackgroundColor(getResources().getColor(R.color.android_red_light)); break;
|
||||
case START: ih.mImg.setBackgroundColor(Color.BLACK); break;
|
||||
case OK: ih.mImg.setBackgroundColor(getResources().getColor(R.color.android_green_light)); break;
|
||||
case CANCELLED: ih.mImg.setBackgroundColor(getResources().getColor(R.color.android_red_light)); break;
|
||||
}
|
||||
|
||||
return convertView;
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.remote.ui.AppsListFragment;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
import org.sufficientlysecure.keychain.util.FabContainer;
|
||||
|
||||
import it.neokree.materialnavigationdrawer.MaterialNavigationDrawer;
|
||||
|
||||
public class MainActivity extends MaterialNavigationDrawer implements FabContainer {
|
||||
|
||||
@Override
|
||||
public void init(Bundle savedInstanceState) {
|
||||
// don't open drawer on first run
|
||||
disableLearningPattern();
|
||||
|
||||
// addMultiPaneSupport();
|
||||
|
||||
// set the header image
|
||||
// create and set the header
|
||||
setDrawerHeaderImage(R.drawable.drawer_header);
|
||||
|
||||
// create sections
|
||||
addSection(newSection(getString(R.string.nav_keys), R.drawable.ic_vpn_key_black_24dp, new KeyListFragment()));
|
||||
addSection(newSection(getString(R.string.nav_encrypt_decrypt), R.drawable.ic_lock_black_24dp, new EncryptDecryptOverviewFragment()));
|
||||
addSection(newSection(getString(R.string.title_api_registered_apps), R.drawable.ic_apps_black_24dp, new AppsListFragment()));
|
||||
|
||||
// create bottom section
|
||||
addBottomSection(newSection(getString(R.string.menu_preferences), R.drawable.ic_settings_black_24dp, new Intent(this, SettingsActivity.class)));
|
||||
addBottomSection(newSection(getString(R.string.menu_help), R.drawable.ic_help_black_24dp, new Intent(this, HelpActivity.class)));
|
||||
|
||||
// if this is the first time show first time activity
|
||||
Preferences prefs = Preferences.getPreferences(this);
|
||||
if (prefs.isFirstTime()) {
|
||||
startActivity(new Intent(this, FirstTimeActivity.class));
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
Intent data = getIntent();
|
||||
// If we got an EXTRA_RESULT in the intent, show the notification
|
||||
if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) {
|
||||
OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT);
|
||||
result.createNotify(this).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fabMoveUp(int height) {
|
||||
Object fragment = getCurrentSection().getTargetFragment();
|
||||
if (fragment instanceof FabContainer) {
|
||||
((FabContainer) fragment).fabMoveUp(height);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fabRestorePosition() {
|
||||
Object fragment = getCurrentSection().getTargetFragment();
|
||||
if (fragment instanceof FabContainer) {
|
||||
((FabContainer) fragment).fabRestorePosition();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,7 +15,6 @@ import android.nfc.Tag;
|
||||
import android.nfc.tech.IsoDep;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
@@ -23,13 +22,11 @@ import org.spongycastle.bcpg.HashAlgorithmTags;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.Iso7816TLV;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* This class provides a communication interface to OpenPGP applications on ISO SmartCard compliant
|
||||
@@ -38,7 +35,7 @@ import java.util.Locale;
|
||||
* For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.GINGERBREAD_MR1)
|
||||
public class NfcActivity extends ActionBarActivity {
|
||||
public class NfcActivity extends BaseActivity {
|
||||
|
||||
// actions
|
||||
public static final String ACTION_SIGN_HASH = "sign_hash";
|
||||
@@ -82,8 +79,6 @@ public class NfcActivity extends ActionBarActivity {
|
||||
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
|
||||
setContentView(R.layout.nfc_activity);
|
||||
|
||||
Intent intent = getIntent();
|
||||
Bundle data = intent.getExtras();
|
||||
String action = intent.getAction();
|
||||
@@ -93,36 +88,46 @@ public class NfcActivity extends ActionBarActivity {
|
||||
mKeyId = data.getLong(EXTRA_KEY_ID);
|
||||
}
|
||||
|
||||
if (ACTION_SIGN_HASH.equals(action)) {
|
||||
mAction = action;
|
||||
mPin = data.getString(EXTRA_PIN);
|
||||
mHashToSign = data.getByteArray(EXTRA_NFC_HASH_TO_SIGN);
|
||||
mHashAlgo = data.getInt(EXTRA_NFC_HASH_ALGO);
|
||||
mServiceIntent = data.getParcelable(EXTRA_DATA);
|
||||
switch (action) {
|
||||
case ACTION_SIGN_HASH:
|
||||
mAction = action;
|
||||
mPin = data.getString(EXTRA_PIN);
|
||||
mHashToSign = data.getByteArray(EXTRA_NFC_HASH_TO_SIGN);
|
||||
mHashAlgo = data.getInt(EXTRA_NFC_HASH_ALGO);
|
||||
mServiceIntent = data.getParcelable(EXTRA_DATA);
|
||||
|
||||
Log.d(Constants.TAG, "NfcActivity mAction: " + mAction);
|
||||
Log.d(Constants.TAG, "NfcActivity mPin: " + mPin);
|
||||
Log.d(Constants.TAG, "NfcActivity mHashToSign as hex: " + getHex(mHashToSign));
|
||||
Log.d(Constants.TAG, "NfcActivity mServiceIntent: " + mServiceIntent.toString());
|
||||
} else if (ACTION_DECRYPT_SESSION_KEY.equals(action)) {
|
||||
mAction = action;
|
||||
mPin = data.getString(EXTRA_PIN);
|
||||
mEncryptedSessionKey = data.getByteArray(EXTRA_NFC_ENC_SESSION_KEY);
|
||||
mServiceIntent = data.getParcelable(EXTRA_DATA);
|
||||
Log.d(Constants.TAG, "NfcActivity mAction: " + mAction);
|
||||
Log.d(Constants.TAG, "NfcActivity mPin: " + mPin);
|
||||
Log.d(Constants.TAG, "NfcActivity mHashToSign as hex: " + getHex(mHashToSign));
|
||||
Log.d(Constants.TAG, "NfcActivity mServiceIntent: " + mServiceIntent.toString());
|
||||
break;
|
||||
case ACTION_DECRYPT_SESSION_KEY:
|
||||
mAction = action;
|
||||
mPin = data.getString(EXTRA_PIN);
|
||||
mEncryptedSessionKey = data.getByteArray(EXTRA_NFC_ENC_SESSION_KEY);
|
||||
mServiceIntent = data.getParcelable(EXTRA_DATA);
|
||||
|
||||
Log.d(Constants.TAG, "NfcActivity mAction: " + mAction);
|
||||
Log.d(Constants.TAG, "NfcActivity mPin: " + mPin);
|
||||
Log.d(Constants.TAG, "NfcActivity mEncryptedSessionKey as hex: " + getHex(mEncryptedSessionKey));
|
||||
Log.d(Constants.TAG, "NfcActivity mServiceIntent: " + mServiceIntent.toString());
|
||||
} else if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) {
|
||||
Log.e(Constants.TAG, "This should not happen! NfcActivity.onCreate() is being called instead of onNewIntent()!");
|
||||
toast("This should not happen! Please create a new bug report that the NFC screen is restarted!");
|
||||
finish();
|
||||
} else {
|
||||
Log.d(Constants.TAG, "Action not supported: " + action);
|
||||
Log.d(Constants.TAG, "NfcActivity mAction: " + mAction);
|
||||
Log.d(Constants.TAG, "NfcActivity mPin: " + mPin);
|
||||
Log.d(Constants.TAG, "NfcActivity mEncryptedSessionKey as hex: " + getHex(mEncryptedSessionKey));
|
||||
Log.d(Constants.TAG, "NfcActivity mServiceIntent: " + mServiceIntent.toString());
|
||||
break;
|
||||
case NfcAdapter.ACTION_TAG_DISCOVERED:
|
||||
Log.e(Constants.TAG, "This should not happen! NfcActivity.onCreate() is being called instead of onNewIntent()!");
|
||||
toast("This should not happen! Please create a new bug report that the NFC screen is restarted!");
|
||||
finish();
|
||||
break;
|
||||
default:
|
||||
Log.d(Constants.TAG, "Action not supported: " + action);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initLayout() {
|
||||
setContentView(R.layout.nfc_activity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the system is about to start resuming a previous activity,
|
||||
* disables NFC Foreground Dispatch
|
||||
|
||||
+6
-5
@@ -15,15 +15,14 @@ import android.nfc.Tag;
|
||||
import android.nfc.tech.IsoDep;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.util.Iso7816TLV;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.util.Iso7816TLV;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -36,7 +35,7 @@ import java.nio.ByteBuffer;
|
||||
* For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.GINGERBREAD_MR1)
|
||||
public class NfcIntentActivity extends ActionBarActivity {
|
||||
public class NfcIntentActivity extends BaseActivity {
|
||||
|
||||
// special extra for OpenPgpService
|
||||
public static final String EXTRA_DATA = "data";
|
||||
@@ -54,8 +53,6 @@ public class NfcIntentActivity extends ActionBarActivity {
|
||||
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
|
||||
setContentView(R.layout.nfc_activity);
|
||||
|
||||
Intent intent = getIntent();
|
||||
Bundle data = intent.getExtras();
|
||||
String action = intent.getAction();
|
||||
@@ -87,7 +84,11 @@ public class NfcIntentActivity extends ActionBarActivity {
|
||||
Log.e(Constants.TAG, "IOException!", e);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initLayout() {
|
||||
setContentView(R.layout.nfc_activity);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+11
-13
@@ -41,8 +41,6 @@ import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.haibison.android.lockpattern.LockPatternActivity;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
|
||||
@@ -108,20 +106,20 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
||||
case RESULT_CANCELED:
|
||||
// The user cancelled the task
|
||||
break;
|
||||
case LockPatternActivity.RESULT_FAILED:
|
||||
// The user failed to enter the pattern
|
||||
break;
|
||||
case LockPatternActivity.RESULT_FORGOT_PATTERN:
|
||||
// The user forgot the pattern and invoked your recovery Activity.
|
||||
break;
|
||||
// case LockPatternActivity.RESULT_FAILED:
|
||||
// // The user failed to enter the pattern
|
||||
// break;
|
||||
// case LockPatternActivity.RESULT_FORGOT_PATTERN:
|
||||
// // The user forgot the pattern and invoked your recovery Activity.
|
||||
// break;
|
||||
}
|
||||
|
||||
/*
|
||||
* In any case, there's always a key EXTRA_RETRY_COUNT, which holds
|
||||
* the number of tries that the user did.
|
||||
*/
|
||||
int retryCount = data.getIntExtra(
|
||||
LockPatternActivity.EXTRA_RETRY_COUNT, 0);
|
||||
// int retryCount = data.getIntExtra(
|
||||
// LockPatternActivity.EXTRA_RETRY_COUNT, 0);
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -256,9 +254,9 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
||||
|
||||
if (keyType == CanonicalizedSecretKey.SecretKeyType.PATTERN) {
|
||||
// start pattern dialog and show progress circle here...
|
||||
Intent patternActivity = new Intent(getActivity(), LockPatternActivity.class);
|
||||
patternActivity.putExtra(LockPatternActivity.EXTRA_PATTERN, "123");
|
||||
startActivityForResult(patternActivity, REQUEST_CODE_ENTER_PATTERN);
|
||||
// Intent patternActivity = new Intent(getActivity(), LockPatternActivity.class);
|
||||
// patternActivity.putExtra(LockPatternActivity.EXTRA_PATTERN, "123");
|
||||
// startActivityForResult(patternActivity, REQUEST_CODE_ENTER_PATTERN);
|
||||
mInput.setVisibility(View.GONE);
|
||||
mProgress.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
|
||||
+13
-40
@@ -43,10 +43,6 @@ import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.haibison.android.lockpattern.LockPatternFragment;
|
||||
import com.haibison.android.lockpattern.LockPatternFragmentOld;
|
||||
import com.haibison.android.lockpattern.widget.LockPatternView;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -56,7 +52,8 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
public class PassphraseWizardActivity extends FragmentActivity implements LockPatternView.OnPatternListener {
|
||||
public class PassphraseWizardActivity extends FragmentActivity {
|
||||
//public class PassphraseWizardActivity extends FragmentActivity implements LockPatternView.OnPatternListener {
|
||||
//create or authenticate
|
||||
public String selectedAction;
|
||||
//for lockpattern
|
||||
@@ -117,10 +114,10 @@ public class PassphraseWizardActivity extends FragmentActivity implements LockPa
|
||||
getActionBar().setTitle(R.string.draw_lockpattern);
|
||||
}
|
||||
// LockPatternFragmentOld lpf = LockPatternFragmentOld.newInstance(selectedAction);
|
||||
LockPatternFragment lpf = LockPatternFragment.newInstance("asd");
|
||||
// LockPatternFragment lpf = LockPatternFragment.newInstance("asd");
|
||||
|
||||
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
||||
transaction.replace(R.id.fragmentContainer, lpf).addToBackStack(null).commit();
|
||||
// FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
||||
// transaction.replace(R.id.fragmentContainer, lpf).addToBackStack(null).commit();
|
||||
}
|
||||
|
||||
public void cancel(View view) {
|
||||
@@ -205,13 +202,11 @@ public class PassphraseWizardActivity extends FragmentActivity implements LockPa
|
||||
writeNFC = false; //just write once
|
||||
Toast.makeText(this, R.string.nfc_write_succesful, Toast.LENGTH_SHORT).show();
|
||||
//advance to lockpattern
|
||||
LockPatternFragmentOld lpf = LockPatternFragmentOld.newInstance(selectedAction);
|
||||
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
||||
transaction.replace(R.id.fragmentContainer, lpf).addToBackStack(null).commit();
|
||||
// LockPatternFragmentOld lpf = LockPatternFragmentOld.newInstance(selectedAction);
|
||||
// FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
||||
// transaction.replace(R.id.fragmentContainer, lpf).addToBackStack(null).commit();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} catch (FormatException e) {
|
||||
} catch (IOException | FormatException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
@@ -226,9 +221,9 @@ public class PassphraseWizardActivity extends FragmentActivity implements LockPa
|
||||
//passwort matches, go to next view
|
||||
Toast.makeText(this, R.string.passphrases_match + "!", Toast.LENGTH_SHORT).show();
|
||||
|
||||
LockPatternFragmentOld lpf = LockPatternFragmentOld.newInstance(selectedAction);
|
||||
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
||||
transaction.replace(R.id.fragmentContainer, lpf).addToBackStack(null).commit();
|
||||
// LockPatternFragmentOld lpf = LockPatternFragmentOld.newInstance(selectedAction);
|
||||
// FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
||||
// transaction.replace(R.id.fragmentContainer, lpf).addToBackStack(null).commit();
|
||||
readNFC = false; //just once
|
||||
} else {
|
||||
//passwort doesnt match
|
||||
@@ -236,9 +231,7 @@ public class PassphraseWizardActivity extends FragmentActivity implements LockPa
|
||||
nfc.setText(R.string.nfc_wrong_tag);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} catch (FormatException e) {
|
||||
} catch (IOException | FormatException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
@@ -356,26 +349,6 @@ public class PassphraseWizardActivity extends FragmentActivity implements LockPa
|
||||
adapter.disableForegroundDispatch(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPatternStart() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPatternCleared() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPatternCellAdded(List<LockPatternView.Cell> pattern) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPatternDetected(List<LockPatternView.Cell> pattern) {
|
||||
|
||||
}
|
||||
|
||||
public static class SelectMethods extends Fragment {
|
||||
// private OnFragmentInteractionListener mListener;
|
||||
|
||||
|
||||
+27
-39
@@ -20,57 +20,56 @@ package org.sufficientlysecure.keychain.ui;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v7.widget.CardView;
|
||||
import android.view.View;
|
||||
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.util.ActionBarHelper;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
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.Log;
|
||||
|
||||
public class QrCodeViewActivity extends ActionBarActivity {
|
||||
public class QrCodeViewActivity extends BaseActivity {
|
||||
|
||||
private ImageView mFingerprintQrCode;
|
||||
private ImageView mQrCode;
|
||||
private CardView mQrCodeLayout;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Inflate a "Done" custom action bar
|
||||
ActionBarHelper.setOneButtonView(getSupportActionBar(),
|
||||
R.string.btn_okay, R.drawable.ic_action_done,
|
||||
setFullScreenDialogClose(
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// "Done"
|
||||
finish();
|
||||
ActivityCompat.finishAfterTransition(QrCodeViewActivity.this);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
setContentView(R.layout.qr_code_activity);
|
||||
|
||||
Uri dataUri = getIntent().getData();
|
||||
if (dataUri == null) {
|
||||
Log.e(Constants.TAG, "Data missing. Should be Uri of key!");
|
||||
finish();
|
||||
ActivityCompat.finishAfterTransition(QrCodeViewActivity.this);
|
||||
return;
|
||||
}
|
||||
|
||||
mFingerprintQrCode = (ImageView) findViewById(R.id.qr_code_image);
|
||||
mQrCode = (ImageView) findViewById(R.id.qr_code_image);
|
||||
mQrCodeLayout = (CardView) findViewById(R.id.qr_code_image_layout);
|
||||
|
||||
mFingerprintQrCode.setOnClickListener(new View.OnClickListener() {
|
||||
mQrCodeLayout.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
ActivityCompat.finishAfterTransition(QrCodeViewActivity.this);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -82,7 +81,7 @@ public class QrCodeViewActivity extends ActionBarActivity {
|
||||
if (blob == null) {
|
||||
Log.e(Constants.TAG, "key not found!");
|
||||
Notify.showNotify(this, R.string.error_key_not_found, Style.ERROR);
|
||||
finish();
|
||||
ActivityCompat.finishAfterTransition(QrCodeViewActivity.this);
|
||||
}
|
||||
|
||||
String fingerprint = KeyFormattingUtils.convertFingerprintToHex(blob);
|
||||
@@ -91,37 +90,26 @@ public class QrCodeViewActivity extends ActionBarActivity {
|
||||
// create a minimal size qr code, we can keep this in ram no problem
|
||||
final Bitmap qrCode = QrCodeUtils.getQRCodeBitmap(qrCodeContent, 0);
|
||||
|
||||
mFingerprintQrCode.getViewTreeObserver().addOnGlobalLayoutListener(
|
||||
mQrCode.getViewTreeObserver().addOnGlobalLayoutListener(
|
||||
new OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
// create actual bitmap in display dimensions
|
||||
Bitmap scaled = Bitmap.createScaledBitmap(qrCode,
|
||||
mFingerprintQrCode.getWidth(), mFingerprintQrCode.getWidth(), false);
|
||||
mFingerprintQrCode.setImageBitmap(scaled);
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
// create actual bitmap in display dimensions
|
||||
Bitmap scaled = Bitmap.createScaledBitmap(qrCode,
|
||||
mQrCode.getWidth(), mQrCode.getWidth(), false);
|
||||
mQrCode.setImageBitmap(scaled);
|
||||
}
|
||||
});
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
Log.e(Constants.TAG, "key not found!", e);
|
||||
Notify.showNotify(this, R.string.error_key_not_found, Style.ERROR);
|
||||
finish();
|
||||
ActivityCompat.finishAfterTransition(QrCodeViewActivity.this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
// custom activity transition to get zoom in effect
|
||||
this.overridePendingTransition(R.anim.qr_code_zoom_enter, android.R.anim.fade_out);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
|
||||
// custom activity transition to get zoom out effect
|
||||
this.overridePendingTransition(0, R.anim.qr_code_zoom_exit);
|
||||
protected void initLayout() {
|
||||
setContentView(R.layout.qr_code_activity);
|
||||
}
|
||||
|
||||
}
|
||||
+18
-52
@@ -27,13 +27,9 @@ import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.NumberPicker;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
@@ -50,13 +46,12 @@ import org.sufficientlysecure.keychain.util.ParcelableFileCache;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import edu.cmu.cylab.starslinger.exchange.ExchangeActivity;
|
||||
import edu.cmu.cylab.starslinger.exchange.ExchangeConfig;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
public class SafeSlingerActivity extends ActionBarActivity {
|
||||
public class SafeSlingerActivity extends BaseActivity {
|
||||
|
||||
private static final int REQUEST_CODE_SAFE_SLINGER = 211;
|
||||
|
||||
@@ -69,51 +64,17 @@ public class SafeSlingerActivity extends ActionBarActivity {
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.safe_slinger_activity);
|
||||
|
||||
mMasterKeyId = getIntent().getLongExtra(EXTRA_MASTER_KEY_ID, 0);
|
||||
|
||||
// NOTE: there are two versions of this layout, for API >= 11 and one for < 11
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
NumberPicker picker = (NumberPicker) findViewById(R.id.safe_slinger_picker);
|
||||
picker.setMinValue(2);
|
||||
picker.setMaxValue(10);
|
||||
picker.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
|
||||
@Override
|
||||
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
|
||||
mSelectedNumber = newVal;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Spinner spinner = (Spinner) findViewById(R.id.safe_slinger_spinner);
|
||||
|
||||
List<String> list = new ArrayList<String>();
|
||||
list.add("2");
|
||||
list.add("3");
|
||||
list.add("4");
|
||||
list.add("5");
|
||||
list.add("6");
|
||||
list.add("7");
|
||||
list.add("8");
|
||||
list.add("9");
|
||||
list.add("10");
|
||||
|
||||
ArrayAdapter<String> dataAdapter = new ArrayAdapter<String>(this,
|
||||
android.R.layout.simple_spinner_item, list);
|
||||
dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
spinner.setAdapter(dataAdapter);
|
||||
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
mSelectedNumber = position + 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
NumberPicker picker = (NumberPicker) findViewById(R.id.safe_slinger_picker);
|
||||
picker.setMinValue(2);
|
||||
picker.setMaxValue(10);
|
||||
picker.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
|
||||
@Override
|
||||
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
|
||||
mSelectedNumber = newVal;
|
||||
}
|
||||
});
|
||||
|
||||
ImageView buttonIcon = (ImageView) findViewById(R.id.safe_slinger_button_image);
|
||||
buttonIcon.setColorFilter(getResources().getColor(R.color.tertiary_text_light),
|
||||
@@ -128,6 +89,11 @@ public class SafeSlingerActivity extends ActionBarActivity {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initLayout() {
|
||||
setContentView(R.layout.safe_slinger_activity);
|
||||
}
|
||||
|
||||
private void startExchange(long masterKeyId, int number) {
|
||||
// retrieve public key blob and start SafeSlinger
|
||||
Uri uri = KeychainContract.KeyRingData.buildPublicKeyRingUri(masterKeyId);
|
||||
@@ -197,7 +163,7 @@ public class SafeSlingerActivity extends ActionBarActivity {
|
||||
certifyIntent.putExtra(CertifyKeyActivity.EXTRA_RESULT, result);
|
||||
certifyIntent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, result.getImportedMasterKeyIds());
|
||||
certifyIntent.putExtra(CertifyKeyActivity.EXTRA_CERTIFY_KEY_ID, mMasterKeyId);
|
||||
startActivityForResult(certifyIntent, KeyListActivity.REQUEST_CODE_RESULT_TO_LIST);
|
||||
startActivityForResult(certifyIntent, 0);
|
||||
|
||||
// mExchangeMasterKeyId = null;
|
||||
}
|
||||
@@ -221,7 +187,7 @@ public class SafeSlingerActivity extends ActionBarActivity {
|
||||
// We parcel this iteratively into a file - anything we can
|
||||
// display here, we should be able to import.
|
||||
ParcelableFileCache<ParcelableKeyRing> cache =
|
||||
new ParcelableFileCache<ParcelableKeyRing>(activity, "key_import.pcl");
|
||||
new ParcelableFileCache<>(activity, "key_import.pcl");
|
||||
cache.writeCache(it.size(), it.iterator());
|
||||
|
||||
// fill values for this action
|
||||
@@ -249,7 +215,7 @@ public class SafeSlingerActivity extends ActionBarActivity {
|
||||
}
|
||||
|
||||
private static ArrayList<ParcelableKeyRing> getSlingedKeys(Bundle extras) {
|
||||
ArrayList<ParcelableKeyRing> list = new ArrayList<ParcelableKeyRing>();
|
||||
ArrayList<ParcelableKeyRing> list = new ArrayList<>();
|
||||
|
||||
if (extras != null) {
|
||||
byte[] d;
|
||||
|
||||
-145
@@ -1,145 +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.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.View;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.util.ActionBarHelper;
|
||||
|
||||
public class SelectPublicKeyActivity extends ActionBarActivity {
|
||||
|
||||
// Actions for internal use only:
|
||||
public static final String ACTION_SELECT_PUBLIC_KEYS = Constants.INTENT_PREFIX
|
||||
+ "SELECT_PUBLIC_KEYRINGS";
|
||||
|
||||
public static final String EXTRA_SELECTED_MASTER_KEY_IDS = "master_key_ids";
|
||||
|
||||
public static final String RESULT_EXTRA_MASTER_KEY_IDS = "master_key_ids";
|
||||
public static final String RESULT_EXTRA_USER_IDS = "user_ids";
|
||||
|
||||
SelectPublicKeyFragment mSelectFragment;
|
||||
|
||||
long mSelectedMasterKeyIds[];
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Inflate a "Done"/"Cancel" custom action bar view
|
||||
ActionBarHelper.setTwoButtonView(getSupportActionBar(), R.string.btn_okay, R.drawable.ic_action_done,
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// ok
|
||||
okClicked();
|
||||
}
|
||||
}, R.string.btn_do_not_save, R.drawable.ic_action_cancel, new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// cancel
|
||||
cancelClicked();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
setContentView(R.layout.select_public_key_activity);
|
||||
|
||||
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
|
||||
|
||||
handleIntent(getIntent());
|
||||
|
||||
// Check that the activity is using the layout version with
|
||||
// the fragment_container FrameLayout
|
||||
if (findViewById(R.id.select_public_key_fragment_container) != null) {
|
||||
|
||||
// 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 (savedInstanceState != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create an instance of the fragment
|
||||
mSelectFragment = SelectPublicKeyFragment.newInstance(mSelectedMasterKeyIds);
|
||||
|
||||
// Add the fragment to the 'fragment_container' FrameLayout
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(R.id.select_public_key_fragment_container, mSelectFragment).commit();
|
||||
}
|
||||
|
||||
// TODO: reimplement!
|
||||
// mFilterLayout = findViewById(R.id.layout_filter);
|
||||
// mFilterInfo = (TextView) mFilterLayout.findViewById(R.id.filterInfo);
|
||||
// mClearFilterButton = (Button) mFilterLayout.findViewById(R.id.btn_clear);
|
||||
//
|
||||
// mClearFilterButton.setOnClickListener(new OnClickListener() {
|
||||
// public void onClick(View v) {
|
||||
// handleIntent(new Intent());
|
||||
// }
|
||||
// });
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
handleIntent(intent);
|
||||
}
|
||||
|
||||
private void handleIntent(Intent intent) {
|
||||
// TODO: reimplement search!
|
||||
|
||||
// String searchString = null;
|
||||
// if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
|
||||
// searchString = intent.getStringExtra(SearchManager.QUERY);
|
||||
// if (searchString != null && searchString.trim().length() == 0) {
|
||||
// searchString = null;
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (searchString == null) {
|
||||
// mFilterLayout.setVisibility(View.GONE);
|
||||
// } else {
|
||||
// mFilterLayout.setVisibility(View.VISIBLE);
|
||||
// mFilterInfo.setText(getString(R.string.filterInfo, searchString));
|
||||
// }
|
||||
|
||||
// preselected master keys
|
||||
mSelectedMasterKeyIds = intent.getLongArrayExtra(EXTRA_SELECTED_MASTER_KEY_IDS);
|
||||
}
|
||||
|
||||
private void cancelClicked() {
|
||||
setResult(RESULT_CANCELED, null);
|
||||
finish();
|
||||
}
|
||||
|
||||
private void okClicked() {
|
||||
Intent data = new Intent();
|
||||
data.putExtra(RESULT_EXTRA_MASTER_KEY_IDS, mSelectFragment.getSelectedMasterKeyIds());
|
||||
data.putExtra(RESULT_EXTRA_USER_IDS, mSelectFragment.getSelectedUserIds());
|
||||
setResult(RESULT_OK, data);
|
||||
finish();
|
||||
}
|
||||
|
||||
}
|
||||
+2
-3
@@ -42,7 +42,6 @@ import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.ListFragmentWorkaround;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.SelectKeyCursorAdapter;
|
||||
@@ -216,7 +215,7 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements T
|
||||
public long[] getSelectedMasterKeyIds() {
|
||||
// mListView.getCheckedItemIds() would give the row ids of the KeyRings not the master key
|
||||
// ids!
|
||||
Vector<Long> vector = new Vector<Long>();
|
||||
Vector<Long> vector = new Vector<>();
|
||||
for (int i = 0; i < getListView().getCount(); ++i) {
|
||||
if (getListView().isItemChecked(i)) {
|
||||
vector.add(mAdapter.getMasterKeyId(i));
|
||||
@@ -238,7 +237,7 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements T
|
||||
* @return
|
||||
*/
|
||||
public String[] getSelectedUserIds() {
|
||||
Vector<String> userIds = new Vector<String>();
|
||||
Vector<String> userIds = new Vector<>();
|
||||
for (int i = 0; i < getListView().getCount(); ++i) {
|
||||
if (getListView().isItemChecked(i)) {
|
||||
userIds.add(mAdapter.getUserId(i));
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user