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:
Vincent Breitmoser
2015-03-02 14:39:28 +01:00
1185 changed files with 24870 additions and 11180 deletions
@@ -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);
@@ -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));
@@ -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()) {
@@ -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 {
@@ -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;
@@ -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,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,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 {
@@ -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) {
@@ -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());
}
}
@@ -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);
}
}
@@ -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);
}
@@ -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;
}
@@ -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);
}
@@ -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);
}
@@ -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;
}
@@ -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];
}
};
}
@@ -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];
}
};
}
@@ -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>() {
@@ -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);
@@ -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));
@@ -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();
@@ -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);
}
}
@@ -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);
}
}
@@ -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());
@@ -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() {
@@ -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 were 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;
@@ -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,
@@ -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;
}
}
@@ -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 {
@@ -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;
@@ -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();
}
}
@@ -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
@@ -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 {
@@ -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";
@@ -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
@@ -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;
}
}
@@ -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 {
@@ -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);
}
}
}
}
@@ -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;
}
@@ -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();
}
}
@@ -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;
@@ -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();
@@ -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);
}
}
}
@@ -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;
@@ -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);
}
}
@@ -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
@@ -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;
}
}
}
@@ -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;
@@ -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.");
@@ -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;
@@ -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!");
@@ -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;
@@ -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));
}
}
@@ -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();
@@ -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);
}
}
@@ -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);
}
@@ -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();
}
}
}
@@ -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;
}
@@ -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
@@ -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 {
@@ -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);
}
}
}
@@ -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!");
}
}
@@ -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 {
@@ -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
*/
@@ -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
@@ -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();
@@ -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();
}
@@ -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());
@@ -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);
}
}
}
@@ -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();
@@ -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);
}
@@ -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);
@@ -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);
}
}
@@ -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);
}
}
@@ -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 cant 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);
}
});
@@ -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<>();
}
}
@@ -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);
}
}
}
@@ -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));
@@ -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);
}
@@ -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
@@ -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);
}
/**
@@ -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 {
@@ -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;
@@ -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);
}
}
@@ -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;
@@ -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();
}
}
@@ -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